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))
>
>

Reply via email to