Am Sa., 29. Okt. 2022 um 04:54 Uhr schrieb Marc Feeley
<[email protected]>:
>
> > On Oct 27, 2022, at 2:01 AM, Marc Nieper-Wißkirchen <[email protected]> 
> > wrote:
> >
> > I am also still hoping for a reply from Marc in the discussion about
> > "weak threads".
> >
> > [...]
>
> Sorry for not getting back sooner.  I have continued my reading of the SRFI 
> 226 spec.  Unfortunately my time is still constrained and the spec is huge so 
> my comments are bound to be more superficial than I’d like.  Here's what 
> stands out.

There's no need to apologize. I am very grateful to all that take the
time to read and think about the specification.

> (call-in-continuation cont thunk) misses an opportunity of having the more 
> general form (call-in-continuation cont proc arg1...) so that it can be 
> called with a procedure and as many arguments as needed.  Instead of 
> (call-in-continuation k (lambda () (values tmp ...)) you could write 
> (call-in-continuation k values tmp ...).  See the definition of the 
> continuation-graft form that you cite:

I will generalize call-in-continuation in this respect.  Thank you for
the suggestion.

[...]

> Note also that one of the main points of the "Better API" paper is to treat 
> continuations as a specific type different from procedures so that the burden 
> of the procedure representation can be avoided (conceptual and also run-time 
> cost for creating the procedure), and also have other operations such as 
> (continuation? obj), (continuation-length k), etc.  I view "continuations as 
> procedures" to be a historical blunder that was motivated by CPS style.  If 
> you have ever tried to explain how call/cc works to students you will 
> probably understand what I'm talking about: "call/cc receives a procedure and 
> calls this procedure with a procedure that represents the continuation".  Too 
> many procedures for most students.  With SRFI 226 there's an opportunity to 
> correct this by making (call-with-non-composable-continuation proc) call proc 
> with a continuation object that is separate from procedures.  It changes very 
> little to the API, except that those continuations have to be called with 
> (call-in-continuation k values ...) or some new more specific procedure 
> (return-to-continuation k ...).

>From a theoretical point of view, I agree with you, and I also see the
point of teaching.  For historical reasons (call/cc), however, I would
like to leave the API as is. Given the presence of call/cc and
existing code, I feel that introducing a new, theoretically more
appealing approach while the historical one is still there leads to
its own share of problems and confusion.

If you want, you can view a continuation (as created by call/cc) as an
element of a new abstract datatype, which, however, happens to be
callable.  To enforce this point of view, SRFI 226 has introduced the
procedure `continuation?`, which checks for whether an object is a
continuation.

> Concerning (thread-terminate! thread), the part "the current thread waits 
> until the termination of thread has occurred" is not ideal.  This was also 
> specified by SRFI 18, and it is OK in a single processor system (because the 
> scheduler is centralized), but I now think it causes issues in a 
> multiprocessor system because it is impossible to predict how long the wait 
> might be.  It is better to have an asynchronous termination, and to use 
> (thread-join! thread), possibly with timeout, when it is necessary to ensure 
> the thread has terminated before proceeding.

I see your point. And adding an extra timeout parameter to
thread-terminate! would make the interface more complicated. The only
problem I see is that this change would introduce a silent
incompatibility with SRFI 18.  Thus, it may be better to drop the name
thread-terminate! and replace it with a different name, like
thread-kill!.

> An alternative to thread-terminate! that is similarly powerful and more 
> elegant is to have an asynchronous (thread-interrupt! thread thunk) procedure 
> that causes thunk to be called at a safe point at the current point of 
> execution of the target thread.  The thunk could then call raise or 
> abort-current-continuation to terminate the thread “from within”, allowing 
> the target thread to do some cleanup.

I don't yet see how this is equally powerful.  What I have in mind is
an implementation of a Scheme REPL where the user starts a program (in
some thread) that goes astray and wishes to abnormally terminate it.
This must work with no cooperation from the program thread.  Raising
an exception or aborting a continuation doesn't necessarily do it.

Also, thread-interrupt! breaches abstraction barriers. Given the code
(begin foo1 foo2) and assuming that evaluating foo1 does not raise any
exception (nor invokes a previously captured continuation), there is a
guarantee that foo2 will always be evaluated once after foo1 (bar
abnormal termination).  Now, using thread-interrupt!, one could
capture a continuation between evaluating foo1 and foo2 and using it
to break the invariant.

> Concerning the addition of (mutex-owner mutex) as a companion to (mutex-state 
> mutex), this has introduced a race condition.  If (eq? (mutex-state mutex) 
> 'owned) is true then extracting the owner thread with (mutex-owner mutex) may 
> return #f.  The API of the SRFI 18 (mutex-state mutex) was designed to not 
> have this race condition.

Yep. I somehow had in mind to query mutex-owner and mutex-state the
other way around, but this actually has the same problem.  Working
around this problem would need unpleasant looping.

I will revert it to the SRFI 18 API or something equivalent.

> The mutex-unlock! procedure's parameter list does not have a timeout 
> parameter, but the description talks about that parameter.  Timeouts are 
> important on all blocking operations.

Indeed.  This oversight has already been reported by Shiro and fixed
in my personal repo.

> The section on thread locals is rather vague and unconvincing.  The 
> thread-specific field has been removed because "If these are needed, weak 
> hash tables could be used instead." but the same can be said for thread 
> locals which are a thin wrapper around weak hash tables indexed by thread.  
> The point of thread-specific was to have constant time (with small constant) 
> access to thread specific data.

Thread locals are natively supported on, for example, POSIX or C11
platforms, thus it makes sense for efficiency reasons to provide them
as a primitive.  The thread-specific field of SRFI 18 has the problem
that it really needs another high-level API to administer it.  On the
other hand, weak hash tables compose well when several libraries in a
program need thread-specific fields.

There is another difference between thread locals and the
thread-specific field: A thread local is really local to the current
thread and a thread can only query its own copy of the value, while a
thread-specific field can be queried for any thread.  Of course, a
high-level API can provide the respective abstraction.  But even then,
a program could break this high-level API by accessing or mutating the
thread-specific field through direct access.

Note that SRFI 226 does not forbid the "specific" fields; an
implementation is free to provide them as an extension.  The usual
data types do not have "specific" fields (e.g. there is no
hash-table-specific), so there are no fundamental reasons why mutexes,
etc., should have specific fields.  One should use wrapper objects
instead.

The latter is a bit different for thread objects because they are
returned by procedures in the SRFI 18/226 API, and the API won't
return wrapper objects.  Still, a single specific field can only be
application specific, not library-specific.  Thus weak hash tables are
the better solution.  If you can think of an even better approach, I
would like to hear about it.

> I’ll have to address weak threads at some other time… (and also the initial 
> continuations section which I have to read carefully).

I am looking forward to reading your comments.

Thanks again,

the other Marc

Reply via email to