Am Sa., 29. Okt. 2022 um 04:54 Uhr schrieb Marc Feeley <xxxxxx@iro.umontreal.ca>: > > > On Oct 27, 2022, at 2:01 AM, Marc Nieper-Wißkirchen <xxxxxx@gmail.com> 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