I agree that thread parameters are not for something expected to be
captured in a future that may be executed on a different thread.  They have
their own place.

Probably what's bugging me is the mutation of locally parameterized thread
parameters.  Without it, I can see their use cases; after all, it behaves
the same way as Gauche's legacy parameters.  But it is what it is.  I see
that SRFI-226 is more consistent, for mutations both outside and
inside parameterize work the same way.

It bothers me that creating thread local for every parameterize does have
overhead, though.  I warn users that thread parameters are not as cheap as
shared parameters in Gauche.

> NB That the mutated value is saved by dynamic-wind's after-thunk is
questionable because a continuation captured before the
> mutation (but within the parameterize form) would then also see the
mutation.

It is the same as mutation of lexically closed variables, so it is actually
desirable for futures to work on arbitrary threads.  But again, users
shouldn't expect thread parameters to work with such futures.

Liquids seem interesting.  I'll see if I can have it in Gauche, too.



On Wed, Sep 13, 2023 at 7:41 PM Marc Nieper-Wißkirchen <
marc.nie...@gmail.com> wrote:

> First of all, here is a test case for SRFI 226:
>
> (test '(3 2)
>       (let ([res '()]
>             [cc #f]
>             [p (make-thread-parameter 1)])
>         (let ([t (make-thread
>                   (lambda ()
>                     (call/cc
>                      (lambda (k0)
>                        (parameterize ([p 2])
>                          (p 3)
>                          (call/cc
>                           (lambda (k1)
>                             (set! cc k1)
>                             (set! res (cons (p) res))
>                             (k0)))
>                          (set! res (cons (p) res)))))))])
>           (thread-start! t)
>           (thread-join! t)
>           (let ([t (make-thread cc)])
>             (thread-start! t)
>             (thread-join! t)
>             (reverse res)))))
>
> The point of thread locals is that they allow us to identify the current
> running thread. Thus, code can detect when it is run in a different thread
> (in fact, as soon as (current-thread) is exposed, code has this ability).
> So it is clear that the behaviour of code can change when it is run in a
> different thread.
>
> Thus, the correct answer to your issue is, in fact, that thread parameters
> would be the wrong tool for the job, at least when they are used in the
> form `(p <new-value>)`. Instead, what you are looking for are liquids as
> described in https://github.com/mnieper/scheme-libraries#liquids.
>
> NB That the mutated value is saved by dynamic-wind's after-thunk is
> questionable because a continuation captured before the mutation (but
> within the parameterize form) would then also see the mutation. You really
> need something like liquids to get it right.
>
>
>
>
> Am Mi., 13. Sept. 2023 um 20:42 Uhr schrieb Shiro Kawai <
> shiro.ka...@gmail.com>:
>
>> Thanks. So here's my main concern.  Suppose the following code.  Again
>> (pause) thing is to avoid Chez's limitation.
>>
>> ====
>> (define cc #f)
>> (define p (make-thread-parameter 1))
>>
>> (define (pause) (sleep (make-time 'time-duration 0 10000)))
>>
>> (fork-thread
>>  (lambda ()
>>    (call/cc
>>     (lambda (k0)
>>       (parameterize ((p 2))
>>         (p 3)
>>         (call/cc
>>          (lambda (k1)
>>            (set! cc k1)
>>            (format #t ">>> ~s\n" (p))
>>            (k0)))
>>         (format #t ">>> ~s\n" (p))
>>         )))
>>    (pause)))
>>
>> (fork-thread cc)
>> ====
>>
>> Chez prints ">>> 3" for both threads.  That is, the parameterization
>> captured by call/cc sees the mutation of the parameterized p across the
>> threads.  Gauche does the same with the equivalent code, even parameters
>> use thread-specific storage.
>> That's because dynamic-wind's after thunk saves the mutated value, and
>> restores it when the continuation is invoked.
>>
>> I think in SRFI-226, the second thread prints ">>> 2", because the
>> mutation is invisible to other threads.
>>
>> The above code can happen when async-await type control flow is
>> implemented in continuations and a thread pool, that is, you won't know if
>> the future is run in the same thread or a different thread.  We could say
>> srfi-226 thead parameters can't be used with such purpose, but that can
>> pose limitations of thread parameters----library calls deep inside the call
>> tree may use a future which is run in a different thread...
>>
>> With Gauche and Chez thread parameters, you can ensure the parameter's
>> portability across threads by parameterizing concerned parameters early in
>> the call tree.
>>
>>
>>
>>
>>
>> On Wed, Sep 13, 2023 at 7:19 AM Marc Nieper-Wißkirchen <
>> marc.nie...@gmail.com> wrote:
>>
>>> The interaction of threads with continuations seems to be complicated in
>>> Chez Scheme (where parameters are implemented using dynamic-wind).  I have
>>> no experience here.
>>>
>>> As far as SRFI 226 is concerned, Gauche is correct.  You can add the
>>> following test to the SRFI 226 unit tests:
>>>
>>> (test 2
>>>       (let ([p (make-parameter 1)])
>>>         (let ([t (make-thread
>>>                   (lambda ()
>>>                     (parameterize ([p 2])
>>>                       ((call/cc
>>>                         (lambda (cc)
>>>                           (lambda () cc)))))))])
>>>           (thread-start! t)
>>>           (let ([cc (thread-join! t)])
>>>             (let ([t (make-thread
>>>                       (lambda ()
>>>                         (cc (lambda () (p)))))])
>>>               (thread-start! t)
>>>               (thread-join! t))))))
>>>
>>> A slightly more elegant version:
>>>
>>> (test 2
>>>       (let ([p (make-parameter 1)])
>>>         (let ([t (make-thread
>>>                   (lambda ()
>>>                     (parameterize ([p 2])
>>>                       (call/cc values))))])
>>>           (thread-start! t)
>>>           (let ([cc (thread-join! t)])
>>>             (let ([t (make-thread
>>>                       (lambda ()
>>>                         (call-in-continuation cc p)))])
>>>               (thread-start! t)
>>>               (thread-join! t))))))
>>>
>>>
>>> Am Mi., 13. Sept. 2023 um 18:38 Uhr schrieb Shiro Kawai <
>>> shiro.ka...@gmail.com>:
>>>
>>>> I inserted pause b/c Chez doesn't seem to allow a continuation to be
>>>> invoked when its capturing thread has already terminated.
>>>>
>>>> Anyway, my thought process is this:
>>>> - The call/cc captures the current parameterization, which is p == 2.
>>>> - Thus, when that continuation is invoked in a different thread, the
>>>> parameterization p == 2 is restored, and prints ">>> 2".
>>>>
>>>> So, SRFI-226 disagrees with Chez and agrees with Gauche, correct?
>>>>
>>>>
>>>>
>>>> On Wed, Sep 13, 2023 at 6:30 AM Marc Nieper-Wißkirchen <
>>>> marc.nie...@gmail.com> wrote:
>>>>
>>>>> Don't you have a race condition in your example because you don't wait
>>>>> for the first thread to terminate?
>>>>>
>>>>> In any case, the current set of parameters (the "current
>>>>> parameterization" in the SRFI 226 language) is part of the current
>>>>> continuation. So when you replace the current continuation, you replace 
>>>>> the
>>>>> current parameterization as well.
>>>>>
>>>>> Does this help?
>>>>>
>>>>> Am Mi., 13. Sept. 2023 um 18:17 Uhr schrieb Shiro Kawai <
>>>>> shiro.ka...@gmail.com>:
>>>>>
>>>>>> I brought it up because the SRFI-226 thread parameter behaves
>>>>>> differently from Gauche legacy parameter which also uses thread-local
>>>>>> storage (The difference actually comes from the fact that parameterize is
>>>>>> realized by dynamic-wind in legacy parameter.)
>>>>>>
>>>>>> But maybe I don't fully understand the implication of interaction of
>>>>>> parameters.   So let me back up one step.
>>>>>>
>>>>>> If I run the following code in Chez REPL, I get ">>>2" from the first
>>>>>> thread, and ">>>1" from the second thread.  If I run the equivalent code 
>>>>>> in
>>>>>> Gauche, the second thread prints ">>>2" as well, for the call/cc captures
>>>>>> the dynamic environment and restores it in the second thead.   Does
>>>>>> SRFI-226 agrees with Chez?
>>>>>>
>>>>>> ====
>>>>>> (define cc #f)
>>>>>> (define p (make-parameter 1))
>>>>>>
>>>>>> (define (pause) (sleep (make-time 'time-duration 0 10000)))
>>>>>>
>>>>>> (fork-thread
>>>>>>  (lambda ()
>>>>>>    (parameterize ((p 2))
>>>>>>      (call/cc (lambda (k) (set! cc k)))
>>>>>>      (format #t ">>> ~s\n" (p))
>>>>>>      (pause))))
>>>>>>
>>>>>> (fork-thread cc)
>>>>>> ====
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> On Tue, Sep 12, 2023 at 9:38 PM Marc Nieper-Wißkirchen <
>>>>>> marc.nie...@gmail.com> wrote:
>>>>>>
>>>>>>> Parameters can be equivalently expressed in terms of fluids (see
>>>>>>> SRFI 226), which mimic variables. Thread parameters are to parameters 
>>>>>>> what
>>>>>>> thread-local fluids are to fluids, so one argument is symmetry.
>>>>>>>
>>>>>>> Often, you want to use thread-local objects in conjunction with the
>>>>>>> parameter mechanism, so thread parameters come in handy. The nice thing
>>>>>>> about them being defined natively is that they can then share the same 
>>>>>>> form
>>>>>>> to parameterize them as non-thread local parameters.
>>>>>>>
>>>>>>> For good "real world" examples, take a look at the documentation of
>>>>>>> the Chez system. Some of its parameters are non-thread local; others are
>>>>>>> thread-local. (NB: Chez's parameters are just parameter-like objects in 
>>>>>>> the
>>>>>>> language of SRFI 226.)
>>>>>>>
>>>>>>>
>>>>>>> Am Mi., 13. Sept. 2023 um 08:19 Uhr schrieb Shiro Kawai <
>>>>>>> shiro.ka...@gmail.com>:
>>>>>>>
>>>>>>>> I'm preparing for a new release of Gauche which supports part of
>>>>>>>> SRFI-226.  While documenting it, I noticed I couldn't recall why thread
>>>>>>>> parameters were added, and what for.  I remember It was added at some
>>>>>>>> point, but I can't find any discussion about it.  I may have just
>>>>>>>> overlooked it.  Can somebody (Marc?) help me recall it?
>>>>>>>>
>>>>>>>> I do remember we discussed whether parameter storage should be
>>>>>>>> thread-specific or not, and I concurred that it should not.  
>>>>>>>> Thread-local
>>>>>>>> storage can be realized by thread locals. Is there a case that you need
>>>>>>>> parameters to be thread specific?
>>>>>>>>
>>>>>>>> --shiro
>>>>>>>>
>>>>>>>>
>>>>>>>>

Reply via email to