From: <don...@is...> - 2009-11-02 22:16:35
|
I begin to understand - this all seems to be related to the implementation choice to use a "shallow binding" scheme. It seems to me that the "deep binding" scheme has the "obviously correct" semantics (meaning, what seems obvious to me, but of course the semantics of lisp was never really defined for multiple threads). Shallow binding was a clever optimization of deep binding that works only in a single threaded environment. I think you're now trying to use shallow binding but changing the semantics of special variables. This is also not entirely clear from the doc. 32.5.2.2. Special Variable Values Every dynamic variable has a global value that can be shared across all MT:THREADs. Bindings of dynamic variables (via LET/LET*/MULTIPLE-VALUE-BIND) are local to MT:THREADs, i.e. every SYMBOL has a different value cell in each MT:THREAD. MT:SYMBOL-VALUE-THREAD can be used to inspect and modify these thread local bindings. Here I would have expected (let (c) (declare (special c)) (foo)) to qualify under the above "Bindings of dynamic variables". One might argue that the very fact that CLHS defines symbol-value seems to require something like shallow binding, but it strikes me that it might also be consistent with CLHS to make symbol-value depend implicitly on (current-thread). And this would better preserve the semantics of special variables. The statement above that every dynamic variable has a global value could still make sense, but then I'd expect to access that not by symbol-value, but by a newly invented global-symbol-value. But I also don't see that such a global value is necessary. Vladimir Tzankov writes: > > What's the difference between a global special and any other kind? > defparameter, defvar and proclaimed specials cause per value cell to Could you possibly mean "per thread value cell"? > be allocated for symbols. Special variables may have different > bindings in different threads. I certainly expect that special variables should be able to have different values in different threads, but I'd expect this to be true whether they are proclaimed (or is that now "declaimed"?) special or only declared special as in (let (c) (declare (special c)) (foo)). So defvar and defparameter create what you call "global" specials? But (declare (special ...)) does not, right? I'm now guessing that (let (x)(declare (special x))...) uses "the global" value cell in the the symbol x rather than a cell for x in the current thread. This violates my intuition/understanding of what special variables are all about. Can you see any justification for such semantics other than ease of implementation? > > How does :initial-bindings compare to something like > > (let ((vars '(a b c))(vals (list a b c))) > > (mt:make-thread > > (lambda () > > (progv vars vals ...)))) > If A, B and C are not special variables they will not have per thread > allocated value cells - bindings that progv will establish will be > shared between all threads (values of A,B and C in all threads will be > the same). Progv variables are all special. I think what you mean is that, because they're not "globally" special (meaning globally declared to be), they won't act like special variables here. > Note also that vars and vals values will be shared between two threads > (in case above it's not important) - in order to avoid this (let > ((vars vars) (vals vals)) (mt:make-thread ...)) may be used. I guess you mean if I had done two different make-thread's in the same (let ((vars ...)(vals ...)) ...) Vladimir Tzankov writes: > On 11/2/09, Don Cohen <don...@is...> wrote: > > How does :initial-bindings compare to something like > > (let ((vars '(a b c))(vals (list a b c))) > > (mt:make-thread > > (lambda () > > (progv vars vals ...)))) > > The main difference is that forms in initial-bindings are eval-ed in > the context of newly created thread. Whoa!! This is even more unexpected! Was that in the doc somewhere? (Of course, in my example I arranged to evaluate things in the calling thread and then quote them so it didn't matter where the evaluation was done.) It seems to me that the current design makes it unnecessarily difficult to pass arguments to the new thread. I'd really like to do something more like (make-thread {optional keyword args if necessary} function . args) where args are evaluated before the thread is created and the values are then given as arguments to the function in the new thread. > ... Thus any reference to thread > specific data (mt:current-thread, mutex ownership change, deferred > interrupts ..) are specific to new thread. Evaluation of these forms > is performed before just before funcall-ing the thread lambda. So far I'm having trouble seeing when/why it's useful to do that evaluation in the new thread. I am seeing that this makes it difficult to pass info from the old thread to the new one. For instance, I had tried to use initial bindings to use the same package in the new thread as in the old one, but it now appears that the "obvious" way of doing this would have failed. > Main purpose of initial-bindings is to perform initialization of some > global data that should not be shared between threads - > *random-state*, *readtable*, *defer-interrupts*, etc - not for passing So far I don't see why it's important not to share the random state. It looks to me like you're really just trying to avoid locking it. Most of the other things in *DEFAULT-SPECIAL-BINDINGS* seem unnecessary or worse, e.g., *print-base* - shouldn't the default be whatever I set the "global" value to? > "arguments" to thread function - see *DEFAULT-SPECIAL-BINDINGS* in > threads.lisp. > When you use initial-bindings it's best to cons your specific data in > front of *DEFAULT-SPECIAL-BINDINGS* or copy and modify it to fit your > needs. |