From: <don...@is...> - 2009-02-24 22:58:11
|
While looking at Vladimir's implementation of read/write locks I begin to notice that there are many other places where returns from interrupts cause problems. (defun rwlock-read-lock (rwlock) (with-lock ((rwlock-lock rwlock)) (when (rwlock-active-writer rwlock) ; write in progress (incf (rwlock-waiting-readers rwlock)) (exemption-wait (rwlock-r-exemption rwlock) (rwlock-lock rwlock)) (decf (rwlock-waiting-readers rwlock))) (incf (rwlock-active-readers rwlock)))) In this code, if a timeout occurs while waiting for a read lock (which seems not at all unlikely), the count of waiting readers ends up incremented, preventing any future writes. In this case we certainly don't want the wait to block the interrupt. Rather we want something more like (incf (rwlock-waiting-readers rwlock)) (unwind-protect (exemption-wait (rwlock-r-exemption rwlock) (rwlock-lock rwlock)) (decf (rwlock-waiting-readers rwlock))) But this raises the (admittedly less likely) possibility of the interrupt occuring between incf and unwind-protect. So maybe (let (incremented) (unwind-protect (progn (setf incremented (incf (rwlock-waiting-readers rwlock))) (exemption-wait (rwlock-r-exemption rwlock) (rwlock-lock rwlock))) (when incremented (decf (rwlock-waiting-readers rwlock))))) Is it possible to interrupt between the incf and the setf? If so, what is to be done about it? Defer interrupts around the setf ? Is there some reasonably easy way to understand/predict where interrupts can occur? More generally, almost any data could be corrupted by interrupting code that writes it and skipping part of that code. This is not a problem if that data is to be discarded, but can be a big problem if that data remains important after the interrupt, such as lock counts. All of this suggests that writing code that works correctly in the presence of timeouts will be substantially harder to write than code that does not. |
From: Sam S. <sd...@gn...> - 2009-02-24 23:20:18
|
since MT is not present in any _released_ clisp version, all MT-related discussion should be on clisp-devel, not clisp-list. |
From: Sam S. <sd...@gn...> - 2009-02-24 23:23:10
|
Don Cohen wrote: > Is it possible to interrupt between the incf and the setf? > If so, what is to be done about it? Defer interrupts around the setf ? > Is there some reasonably easy way to understand/predict where > interrupts can occur? clisp/doc/multithread.txt says: Hash Tables and Sequences ------------------------- Nothing is ever locked, so the user is required to use locks when sharing HASH-TABLEs and SEQUENCEs between threads. If two threads evaluate (INCF (GETHASH x global-ht 0)), the results are undefined. -- But this doesn't allow the programmer to fulfill the Parallelizability Principle easily. Program PRELUDE: (defparameter global-ht (make-hash-table)) Program A: (setf (gethash 'a global-ht) 'aaaa) Program B: (setf (gethash 'b global-ht) 'bbbb) The Parallelizability Principle implies that one should have an easy way to declare that global-ht is shared, without modifying the programs A and B. The obvious proposal is a change in the PRELUDE: (defparameter global-ht (make-hash-table :lockable t)) While this automatic locking will indeed work when no keys are shared, this is not a universal solution: Program A: (incf (gethash 10 global-ht 0)) Program B: (incf (gethash 10 global-ht 0)) It is possible that both GETHASH calls will happen before both PUTHASH calls unless both INCF forms are guarded with a lock. Instead of making GLOBAL-HT an instance of LOCK (and relying on some magic which cannot always work), one needs to create an explicit lock with (defparameter global-ht-lock (ext:make-lock)) and wrap all his GLOBAL-HT accesses with (with-lock (global-ht-lock) (incf (gethash 'a global-ht 0))) The bottom line is: programs that use global variables do not fall under the Parallelizability Principle because they share application objects with programs running in other threads. -- Automatic locking will impose an unjustifiable penalty on HASH-TABLEs and SEQUENCEs local to threads. It is also consistent with the usual Common Lisp approach of http://www.lisp.org/HyperSpec/Body/sec_3-6.html: The consequences are undefined when code executed during an object-traversing operation destructively modifies the object in a way that might affect the ongoing traversal operation... http://www.lisp.org/HyperSpec/Body/sec_18-1-2.html If an object O1 is used as a key in a hash table H and is then visibly modified with regard to the equivalence test of H, then the consequences are unspecified if O1 is used as a key in further operations on H... I.e., if you want to shoot yourself, it is YOUR responsibility to wear armor. |