Am Di., 10. Aug. 2021 um 00:04 Uhr schrieb John Cowan <xxxxxx@ccil.org>:


Could you briefly say why you treat promises special?

Primarily because that is the way they are already packaged in R7RS: this allows (import (scheme lazy)) to be directly replaced by (import (srfi :224 promises)).

That makes sense.

In this regard, one may also contemplate the following:

- In R6RS, but not in R7RS, the identifiers with-exception-handler/guard/raise/raise-continuable are outside the base library.  Would the same reasoning apply to an R6RS system?
- That said, the promise bindings of SRFI 226 are on 90%-compatible with R7RS-small (cf. the recent discussion with Marc), so an extra library makes sense (at least until the R7RS-small bindings are deprecated).  The SRFI 226 call/cc is compatible with R7RS, but it isn't with R6RS.  But here, the reason is minor:  R6RS's call/cc must raise an assertion violation if called with two arguments.  I see three possible resolutions: Do nothing; put call/cc in its extra library; rename the two-argument version of call/cc to call/cc/prompt or something like that.
 
Alternatively, it could make sense just to put `thread-start!` in its own library (so that the rest can get dummy implementations, making it easier to write portable programs).  But I haven't checked yet whether the latter makes sense.

In my view, libraries are primarily about what users see as a conceptual unity, not just an implementation convenience.

I think I see it the same way.
 
I am also unsure what it means to execute a thread program on a single-threaded implementation, at least if the threads are not all doing the same thing.   Using threads for pipelining, for example, is just not going to work.

My suggestion of exposing everything but thread-start! in a library was based on the idea that it could make it possible to write libraries that are "multithreading-ready" when used in programs that use multiple threads.
 
Condition-variable-signal! only wakes up a thread that is waiting on that condition variable, so I don't see how the randomness has be be a problem.

On multiprocessor systems it is common for the thread system to change a -signal to a -broadcast when there is more than one thread waiting on different CPUs: this is known as the spurious wakeup problem.  Therefore, when a thread wakes up it has to check that its ready-to-go predicate is now true, and if not to wait again.  so there is a slight overhead for each thread doing that .  But since scheduling is unfair, it is possible that some threads will simply never make progress if the implementation chooses the same thread all the time.  This is not the case for broadcast!; all threads wake up and all but one wait again.

I'm no expert for Posix or C11 threads, but isn't it the case that there can always be spurious wake-ups when a thread waits for condition variables?  Is there any guarantee that this won't happen when only -broadcast! is used?A
As long as -signal! does not add any implementation obstacles, I am leaning towards leaving it in, maybe with a note containing your criticism.


As for thread-terminate!:  I would like to hear Marc's opinion on it as well.  SRFI 18 has the concept of abandoned mutexes to resolve (some of?) the problems stated in the Java document you cited.  There may be use cases of thread-terminate!.  For example, when a Scheme (or some other language) interpreter is written in Scheme and one of its threads executes long-running library code, I may want to be able to kill it quickly.

Notably, the C11 thread API mimics the Posix API but leaves out pthread_cancel.

I have taken another look at it.  Even with the concept of abandoned mutexes, I don't see a way to use thread-terminate! safely.  If a thread was using a shared hash table (protected by a lock) when it was terminated, the hash table could be left in an inconsistent state.  Accessing the hash table again would lead to an abandoned mutex exception, which is good for error detection, but the program would still have no way to repair the corrupted data.

Posix has the concept of a cancelability state and has cleanup handlers.  Without such devices, I start to believe that thread-terminate! has no place in a well-written program.  With Posix's extra bells-and-whistles (which are not part of SRFI 18), canceling threads may be an option.

I think that this question is less about implementability (because under the hood it can be implemented with polling alongside the GC handler), but about whether an API can be constructed that allows one to write safe multi-threaded programs.
 
 
3) In place of time objects, I recommend that thread-join! be split into thread-wait! (forever), thread-wait-for! (specifying a number of jiffies), and thread-wait-until! (specifying a future jiffy).  Likewise with thread-sleep!, mutex-lock!, and mutex-unlock!.

The role of time objects in SRFI 18 is to provide an absolute time.  Can this be done with jiffies?  SRFI 18 uses different representations of real numbers and time objects to support ad-hoc overloading (absolute vs relative vs no timeout), so I guess this is why you would like to split all the procedures, right?
 
4) More tentative: I am not convinced of the utility of the -name and -specific fields of mutexes and condition variables (as opposed to threads).    They seem to be there purely for the sake of symmetry.  (Marc F?)

I agree that it is unusual to offer such specific fields.  For example, built-in hashtables or ports do not offer it.  The specific field for threads has the problem that it can only be used once.  For example, your future implementation, John, makes use of it and so it cannot be used by any other mechanism anymore, leading to libraries that become harder to compose.

A good suggestion could therefore be to first remove the specific fields on mutexes and condition variables (because they are not strictly necessary) and then to replace the specific field of threads with a dictionary behind the scenes that is modelled as continuation marks are modelled:

(make-thread-specific-key), (thread-specific-key? obj), (thread-specific-ref thread key), (thread-specific-set! thread key).


Hmmm... Thinking a bit more about it, maybe the specific field for threads can be dropped altogether as well.  A weak hash table can be used for associating data to thread objects.  And we have parameters for thread-local storage:

(define tlvar (make-parameter 0))
(thread
  (parameterize ((tlvar 0)) ; allocate new cell!
    ...
    (tlvar 1)
    ...
    (tlvar)))