[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

DYNAMIC-WIND vs. multi-processing

    Except for top-level variables, CALL-WITH-CURRENT-CONTINUATION
    captured the whole state of a computation.

I do not think so. Consider the file system. The characters you typed in from
your keyboard. The time of day. The program store, including non-"top level"
variables and arrays. This "captures all the state" notion is definitely not a
reason to defend CALL/CC. In real programs, there's a lot it doesn't capture.

I also do not believe in the idea of "a" computational top-level. There never
is one, barring maybe the boot prom.  Scheme programs do not have to have the
model of running inside a read-eval-print loop. So saying that you grab the
entire future of the computation back to the top level begs the question.
Which top level?  You need operators to define "top level" with respect to a
particular computational point of view. I do not think deeply about these
things, but some people do: Felleisen, Danvy, and Filinski have written a lot
of papers dealing with these issues. So, again, I think CALL/CC comes up a
little short of being the right thing.

Generally, I think CALL/CC is not the way to do threads. For one, it allows
you to invoke a continuation multiple times, which never happens with a thread
continuation.  For two, it is a serial model of concurrency, which does not
cut the mustard.

Further, I do not think DYNAMIC-WIND-invoked-on-every-thread-switch is the way
to manage thread-shared state that needs to be in a particular configuration
while some thread is running -- what Gerry says RMS thought it up for.  Again,
this model doesn't handle true parallel execution.  An example: suppose I
wanted my threads to each have their own Unix current working directory, so I
had DYNAMIC-WIND do chdir's going in and cwd's coming out. Then on every
thread switch, I could do an (expensive, slow) mutation of the shared process
state to put each thread in his own configuration of that state. But how do I
model running threads on a parallel processor, where more than one thread can
execute at once? CALL/CC and DYNAMIC-WIND don't handle this. I think a thread
model should have semantics that are independent of whether you are using a
uniprocessor or multiprocessor implementation.

Another contrived example: a thread corresponds to a vertex in some tree data
structure your program has built.  Whenever you switch into a thread, you
re-root the tree so the resumed thread is the new root. OK, fine, DYNAMIC-WIND
will take care of you. BUT: what happens when you want to run your algorithm
on a parallel machine? It's just not a good model.

I guess DYNAMIC-WIND is useful for managing thread state if you restrict your
"concurrency" to a sort of coroutines model. That's doesn't make it a
super-useful operator.

I do think DYNAMIC-WIND is useful for doing cleanup -- e.g., releasing a
resource -- after a computation finishes, either normally or by aborting. This
cleanup action may be irreversible (e.g., deleting a file), and the
DYNAMIC-WIND's entrance thunk may not be able to recreate it, so you can't
just throw out and in on every thread switch.

However, this use is frequently screwed up by the ability of retained
continuations to "fool" the unwinder into thinking that you've finished
with a computation when you really haven't -- you'll throw back into it
later. This makes me less enthusiastic about DYNAMIC-WIND.

I think that it might be nice to recognise the unlimited extent of
CALL/CC-grabbed continuations, and really provide for a true cleanup-on-exit
feature with a procedure
	(when-finished thunk cleanup-thunk)
This call returns the value of (THUNK). However, sometime after
WHEN-FINISHED's (and THUNK's) continuation is transferred to for the last
time, CLEANUP-THUNK will be run.

	- On a normal return, with no retained inner continuations, this
	  is just like UNWIND-HANDLER
	- If you retain a continuation so that you can return through
	  THUNK multiple times, you may have to delay running the cleanup
	  action until THUNK's continuation is gc'd.

So, in other words, you can safely arrange for some resource to be available
to the computation THUNK, and also know that it will be released when the
computation is finished. Garbage collection does not handle all resource
allocation problems -- you can't rely on it to free up temp files, for
example. Another example: WHEN-FINISHED provides a more general solution to
the CALL-WITH-INPUT-FILE problem to which you referred.

One way to look at this is that people usually think of procedure frames as a
stack-allocated resource. If you return through one, or throw past it, it is
freed by a non-heap mechanism. But as soon as you grab a continuation, the
frame becomes a heap resource, subject to gc. WHEN-FINISHED allows you to do
that with other resources. If you return through or throw past the allocation
point, it is freed. If you grab a continuation that includes the allocation
point, it becomes heap-managed. CLEANUP-THUNK is just the deallocation
hook that will be used by the stack manager or heap manager, as appropriate.

If you do that, I am really running out of reasons to have DYNAMIC-WIND at

    Alternately, one might say that users should just avoid the use of
    DYNAMIC-WIND if they want to multi-process.

If users want to multi-process, they should use a concurrent language, 
which straight, simple R4RS Scheme is not. CALL/CC with or without
DYNAMIC-WIND is not the way to do concurrency.