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
