[thread] release lock before cond.var. notified?
Hi, Consider a boolean variable, a mutex and a condition variable: // simplified code snippet begin bool Continue = false; boost::mutex someMutex; boost::condition_variable someCondVar; // simplified code snippet end A number of (boost-)threads check if the boolean variable is set to true in order to continue execution, or otherwise wait for this to happen: // simplified code snippet begin boost::unique_lockboost::mutex threadLock(someMutex); while (Continue == false) { someCondVar.wait(threadLock); } // simplified code snippet end and a single controller thread is responsible for setting Continue to true (see snippets below). Now the question: Is there a strong point pro or against releasing controllerLock before the notify_all-signal is sent? E.g. is there preference for the first or second variant as below (see placement of curly brackets!), or even a need for one to ensure correctness? // simplified code snippet begin - Variant 1 // notify_all is issued while controllerLock is still being held { boost::unique_lockboost::mutex controllerLock(someMutex); Continue = true; someCondVar.notify_all(); } // simplified code snippet end // simplified code snippet begin - Variant 2 // notify_all is issued when controllerLock has already been released { boost::unique_lockboost::mutex controllerLock(someMutex); Continue = true; } someCondVar.notify_all(); // simplified code snippet end Thanks for your input and help, Thomas
On Sep 9, 2013 3:34 AM, "Thomas M"
Hi,
Consider a boolean variable, a mutex and a condition variable:
// simplified code snippet begin bool Continue = false; boost::mutex someMutex; boost::condition_variable someCondVar; // simplified code snippet end
A number of (boost-)threads check if the boolean variable is set to true
in order to continue execution, or otherwise wait for this to happen:
// simplified code snippet begin boost::unique_lockboost::mutex threadLock(someMutex); while (Continue == false) { someCondVar.wait(threadLock); } // simplified code snippet end
and a single controller thread is responsible for setting Continue to
Now the question: Is there a strong point pro or against releasing controllerLock before the notify_all-signal is sent? E.g. is there preference for the first or second variant as below (see
true (see snippets below). placement of curly brackets!), or even a need for one to ensure correctness?
// simplified code snippet begin - Variant 1 // notify_all is issued while controllerLock is still being held { boost::unique_lockboost::mutex controllerLock(someMutex); Continue = true; someCondVar.notify_all(); } // simplified code snippet end
// simplified code snippet begin - Variant 2 // notify_all is issued when controllerLock has already been released { boost::unique_lockboost::mutex controllerLock(someMutex); Continue = true; } someCondVar.notify_all(); // simplified code snippet end
My preference is for variant 2. It might be an uncommon case, but it is possible for a context switch at any time, and that can lead to a case where the other thread is notified and will yry to acquire the lock, but the lock is still held by this thread for unknown amount of time. Brian
Am 09.09.2013 16:44, schrieb Brian Budge:
On Sep 9, 2013 3:34 AM, "Thomas M"
mailto:firespot71@gmail.com> wrote: Hi,
Consider a boolean variable, a mutex and a condition variable:
// simplified code snippet begin bool Continue = false; boost::mutex someMutex; boost::condition_variable someCondVar; // simplified code snippet end
A number of (boost-)threads check if the boolean variable is set to
true in order to continue execution, or otherwise wait for this to happen:
// simplified code snippet begin boost::unique_lockboost::mutex threadLock(someMutex); while (Continue == false) { someCondVar.wait(threadLock); } // simplified code snippet end
and a single controller thread is responsible for setting Continue to
true (see snippets below).
Now the question: Is there a strong point pro or against releasing controllerLock before the notify_all-signal is sent? E.g. is there preference for the first or second variant as below (see placement of curly brackets!), or even a need for one to ensure correctness?
// simplified code snippet begin - Variant 1 // notify_all is issued while controllerLock is still being held { boost::unique_lockboost::mutex controllerLock(someMutex); Continue = true; someCondVar.notify_all(); } // simplified code snippet end
// simplified code snippet begin - Variant 2 // notify_all is issued when controllerLock has already been released { boost::unique_lockboost::mutex controllerLock(someMutex); Continue = true; } someCondVar.notify_all(); // simplified code snippet end
My preference is for variant 2. It might be an uncommon case, but it is possible for a context switch at any time, and that can lead to a case where the other thread is notified and will yry to acquire the lock, but the lock is still held by this thread for unknown amount of time.
But am I correct that both variants are safe, so neither deadlocks nor races may occur under any circumstances (ignore any issues outside of the code snippet above)? Once the notify-signal is issued each waiting thread needs to acquire the lock, and hence they get the lock strictly sequentially. I.e. most of them have to wait anyway while some other thread has the mutex locked. Variant 1 has the advantage of 'bundling' the continuation and notification signal, and it also notifies other threads at the earliest possible point - of course given if it's technically allowed to do it while still locking the mutex, which I think is ok but not 100% sure. Thomas
On Mon, Sep 9, 2013 at 2:35 PM, firespot71
But am I correct that both variants are safe, so neither deadlocks nor races may occur under any circumstances (ignore any issues outside of the code snippet above)? Once the notify-signal is issued each waiting thread needs to acquire the lock, and hence they get the lock strictly sequentially. I.e. most of them have to wait anyway while some other thread has the mutex locked. Variant 1 has the advantage of 'bundling' the continuation and notification signal, and it also notifies other threads at the earliest possible point - of course given if it's technically allowed to do it while still locking the mutex, which I think is ok but not 100% sure.
Thomas
Both variants are safe, but normal usage would say that variant 2 is at least as efficient as variant 1. By normal usage I mean that whichever thread is notified will need to acquire the lock before it can do anything useful. Brian
On 9/09/2013 10:34 p.m., Quoth Thomas M:
Now the question: Is there a strong point pro or against releasing controllerLock before the notify_all-signal is sent? E.g. is there preference for the first or second variant as below (see placement of curly brackets!), or even a need for one to ensure correctness?
The normal problem, IIRC, is that notify_all() will only wake up those threads that are actually sleeping on the condition variable already. If you release the lock early, then another thread might enter the locked region, discover that the condition is false, and be about to sleep on the condition variable when the notify is triggered -- it then goes to sleep permanently because it missed the wakeup event.
// simplified code snippet begin - Variant 2 // notify_all is issued when controllerLock has already been released { boost::unique_lockboost::mutex controllerLock(someMutex); Continue = true; } someCondVar.notify_all(); // simplified code snippet end
Given that you are updating the condition within the mutex I believe that this is safe from the above issue. However it still may not be safe due to other factors -- for instance, if the memory containing someCondVar becomes eligible for deletion once the condition is true (which happens more often than you'd think, particularly when the condition includes some sort of termination flag/event) then variant 1 is still safe while variant 2 is not. In terms of efficiency, variant 2 is probably more efficient than variant 1 on a uniprocessor system, but on a multicore system they're probably about the same. (As on uniprocessor there is a chance that the notify could cause an immediate preemption, lockout, and switchback with variant 1, while on a multicore the notifying thread should be able to continue to release the lock.)
On Mon, Sep 9, 2013 at 3:44 PM, Gavin Lambert
On 9/09/2013 10:34 p.m., Quoth Thomas M:
Now the question: Is there a strong point pro or against releasing controllerLock before the notify_all-signal is sent? E.g. is there preference for the first or second variant as below (see placement of curly brackets!), or even a need for one to ensure correctness?
The normal problem, IIRC, is that notify_all() will only wake up those threads that are actually sleeping on the condition variable already. If you release the lock early, then another thread might enter the locked region, discover that the condition is false, and be about to sleep on the condition variable when the notify is triggered -- it then goes to sleep permanently because it missed the wakeup event.
You still have to guarantee that any data that needs to live in the critical section is covered by the mutex lock. You cannot change or check the condition outside the mutual exclusion region. That's a bad race. Note that this is not the same as holding the lock when notifying the condition variable.
// simplified code snippet begin - Variant 2 // notify_all is issued when controllerLock has already been released { boost::unique_lockboost::mutex controllerLock(someMutex); Continue = true; } someCondVar.notify_all(); // simplified code snippet end
Given that you are updating the condition within the mutex I believe that this is safe from the above issue. However it still may not be safe due to other factors -- for instance, if the memory containing someCondVar becomes eligible for deletion once the condition is true (which happens more often than you'd think, particularly when the condition includes some sort of termination flag/event) then variant 1 is still safe while variant 2 is not.
See my above comment.
In terms of efficiency, variant 2 is probably more efficient than variant 1 on a uniprocessor system, but on a multicore system they're probably about the same. (As on uniprocessor there is a chance that the notify could cause an immediate preemption, lockout, and switchback with variant 1, while on a multicore the notifying thread should be able to continue to release the lock.)
My hunch is that you're right that it can be quite bad in the uniprocessor case, and that it's probably not as bad in the multiprocessor case. Brian
participants (4)
-
Brian Budge
-
firespot71
-
Gavin Lambert
-
Thomas M