Hello,
I've encountered two cases that affect behavior of code but aren't clarified in the documentation:
1. At least one implementation (SBCL) requires that the lock on which condition-wait is called is to be held by the thread that calls condition-notify on the corresponding CV. This should be mentioned in the documentation as a requirement.
2. It is undefined what happens when release-lock is called inside a with-lock-held on the same lock. SBCL and Clozure appear to handle this "properly," although I think the documented behavior should be the same as with regular release-lock ("It is an error to call this unless the lock has previously been acquired (and not released) by the same thread," that is, it should be an error to call release-lock inside a with-lock-held).
Thank you, Vladimir
On Tue, 30 Nov 2010 00:02:05 -0500, Vladimir Sedach said:
Hello,
I've encountered two cases that affect behavior of code but aren't clarified in the documentation:
- At least one implementation (SBCL) requires that the lock on which
condition-wait is called is to be held by the thread that calls condition-notify on the corresponding CV. This should be mentioned in the documentation as a requirement.
Yes, this is a standard, but ill-documented, requirement for condition variables. The lock is needed to synchronize the notify with the change to the underlying data in the application.
- It is undefined what happens when release-lock is called inside a
with-lock-held on the same lock. SBCL and Clozure appear to handle this "properly," although I think the documented behavior should be the same as with regular release-lock ("It is an error to call this unless the lock has previously been acquired (and not released) by the same thread," that is, it should be an error to call release-lock inside a with-lock-held).
I think release-lock can be allowed inside with-lock-held, as long as you call acquire-lock before exiting the body.
- At least one implementation (SBCL) requires that the lock on which
condition-wait is called is to be held by the thread that calls condition-notify on the corresponding CV. This should be mentioned in the documentation as a requirement.
Yes, this is a standard, but ill-documented, requirement for condition variables. The lock is needed to synchronize the notify with the change to the underlying data in the application.
At least on Clozure, B-T implements condition-wait/notify with semaphores, so this appears to work fine, even though on other implementations it might lead to race conditions.
I think release-lock can be allowed inside with-lock-held, as long as you call acquire-lock before exiting the body.
Good point. There's a lot of variation of this in the implementations - for some implementations B-T uses the with-lock-held equivalent provided by the implementation, which might have different semantics, and in other implementations (like SBCL) all locks are recursive, so it's harder to track down lock/unlock mismatch errors.
Now that I think about it some more it might be a good idea to not just specify more semantics in the documentation, but provide assertions for the specified behavior. It would be nice to do this based on optimize declaration safety level. Does anyone know of a library that provides access to declarations at macro-expansion time like CLtL2's Environments (http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node102.html)?
Vladimir
On Tue, 30 Nov 2010 13:27:13 -0500, Vladimir Sedach said:
- At least one implementation (SBCL) requires that the lock on which
condition-wait is called is to be held by the thread that calls condition-notify on the corresponding CV. This should be mentioned in the documentation as a requirement.
Yes, this is a standard, but ill-documented, requirement for condition variables. The lock is needed to synchronize the notify with the change to the underlying data in the application.
At least on Clozure, B-T implements condition-wait/notify with semaphores, so this appears to work fine, even though on other implementations it might lead to race conditions.
OK, I see what you mean -- the implementation of condition-notify might not be thread safe within itself unless the lock is held.
I was referring to a different problem though. If you call condition-notify without holding the lock, then you can lose notifications, i.e. condition-wait might never wake up.
A typical use of condition-wait is like this:
(defun wait-for-it () (with-lock-held (lock) (loop (when (application-state-says-ready-p) (return)) ;; ** (condition-wait cv lock))))
and the corresponding use of condition-notify should be like this:
(defun provide-it () (with-lock-held (lock) (setf (application-state-says-ready-p) t) (condition-notify cv)))
You must use with-lock-held to change the application state and notify atomically w.r.t. the call to application-state-says-ready-p and condition-wait. This is because, at least according to the pthreads definition, condition variables have no memory of calls to notify that occurred when noone was waiting. If provide-it can run (in another thread) at the point labelled ** in wait-for-it, then the notify will be lost. The lock prevents provide-it from doing that.
bordeaux-threads-devel@common-lisp.net