Thank you! Am Mo., 7. Nov. 2022 um 19:19 Uhr schrieb Marc Feeley <[email protected]>: > > > > On Nov 7, 2022, at 4:41 AM, Marc Nieper-Wißkirchen > > <[email protected]> wrote: > > > > Hey Marc, > > > > could you describe the exact semantics of Gambit's thread-interrupt! > > or give me a link to where it is documented? > > > > Specifically, what happens when the thunk returns normally? > > > > We talked about capturing a continuation inside the thunk, which is > > related to the question. > > > > Thanks, > > > > Marc > > > > The API and semantics of thread-interrupt! has evolved over time since the > introduction of threads in Gambit v4.0 (~2000). The basic idea is to force a > runnable or blocked thread to immediately execute a call to a thunk, > regardless of what that thread is currently doing. Conceptually each thread > executes a series of atomic actions (“atomic” in the sense that they are > operations at a certain level of abstraction of the virtual machine, such as > a call to “cons”, “car”, “pair?”, etc). But note that some Scheme predefined > procedures, such as “append”, “map”, etc are not atomic and are a series of > atomic actions. The thread interrupt mechanism inserts the call to the thunk > between such atomic actions thus ensuring that interrupts happen at “safe > places” (for the Scheme virtual machine, which does not mean that it is safe > for the logic of the program, which is the programmer’s concern). > Conceptually, if the thread was about to evaluate <expr>, it replaces this by > the evaluation of (begin (thunk) <expr>) so that the thunk’s result is > ignored and the thunk is called with the thread’s current continuation as a > parent, including the dynamic environment. > > There are two flavors of interrupts. They are accessible with these internal > primitives of the runtime library: > > (##thread-int! <thread> <thunk>) > (##thread-call <thread> <thunk>) > > They both execute a call to <thunk> in the thread <thread>, but ##thread-int! > is asynchronous (the calling thread does not wait for <thread> to be done > executing the call to <thunk>) and ##thread-call is synchronous (it waits > until <thread> is done executing the call to <thunk> and it returns the value > returned by <thunk>).
So, at least in the case of ##thread-call, the continuation of the call to thunk is not precisely the continuation that will evaluate <expr> next but a continuation that sends the return values of <thunk> to the calling thread before continuing with <expr>, right? Does Gambit handle signals through thread-int!, e.g. as in the following? (##thread-int! <thread> (lambda () (raise-continuable <signal-condition>))) or (##thread-int! <thread> (lambda () (signal-interrupt <info>))) where (define (signal-interrupt info) ((current-signal-interrupt-handler) info)) > > Note that ##thread-call can be written on top of ##thread-int! (using a mutex > to wait for the result). So ##thread-int! is the fundamental primitive. > > As an example, the code attached below will print this: > > (started #<thread #2> a) > (started #<thread #3> b) > (called ##thread-int! #<thread #2>) > (called ##thread-int! #<thread #3>) > (ending ##thread-int! #<thread #3> b) > (ending ##thread-int! #<thread #2> a) > (result of ##thread-call #<thread #2> a) > (result of ##thread-call #<thread #3> b) > (done) > > The code creates two threads “a” and “b” and interrupts them asynchronously > and synchronously. Each thread dynamically binds the parameter object “p” to > the name of the thread to demonstrate how interrupts interact with the > dynamic environment. > > The ##thread-call procedure is a convenience. I’m still unsure if a timeout > parameter should be added because for those cases where a timeout is required > it is probably more expressive to rewrite the code with a ##thread-int! and > an explicit synchronization logic (that goes beyond a simple timeout). > > Marc > > > > (define p (make-parameter 'init)) > > (define (start name mut) > (thread-start! > (make-thread > (lambda () > (parameterize ((p name)) > (pp (list 'started (current-thread) name)) > (mutex-unlock! mut) > (let loop ((i 50)) > (if (> i 0) > (begin > (thread-sleep! 0.1) > (loop (- i 1)))))))))) > > (define (make-locked-mutex) > (let ((mut (make-mutex))) > (mutex-lock! mut) > mut)) > > (define a-started-mut (make-locked-mutex)) > (define b-started-mut (make-locked-mutex)) > > (define a (start 'a a-started-mut)) > (define b (start 'b b-started-mut)) > > (mutex-lock! a-started-mut) ;; wait for a to start > (mutex-lock! b-started-mut) ;; wait for b to start > > (##thread-int! a (lambda () > (thread-sleep! 1) > (pp (list 'ending '##thread-int! (current-thread) (p))))) > > (pp (list 'called '##thread-int! a)) > > (##thread-int! b (lambda () > (thread-sleep! 0.5) > (pp (list 'ending '##thread-int! (current-thread) (p))))) > > (pp (list 'called '##thread-int! b)) > > (pp (##thread-call a (lambda () > (thread-sleep! 0.5) > (list 'result 'of '##thread-call (current-thread) > (p))))) > > (pp (##thread-call b (lambda () > (thread-sleep! 0.5) > (list 'result 'of '##thread-call (current-thread) > (p))))) > > (pp (list 'done)) > >
