Using std :: condition_variable with atomic <bool>

There are a few atom related questions about SO, and others that relate to std :: condition_variable. But my question is, is my usage correct below?

Three threads, one ctrl thread, which does the preparatory work before pausing the other two threads. Ctrl thread can also pause worker threads (sender / receiver) while they are in their hard send / receive loops. The idea of ​​using an atom is to make narrow loops faster if the logical value for the pause is not set.

class SomeClass { public: //... // Disregard that data is public... std::condition_variable cv; // UDP threads will wait on this cv until allowed // to run by ctrl thread. std::mutex cv_m; std::atomic<bool> pause_test_threads; }; void do_pause_test_threads(SomeClass *someclass) { if (!someclass->pause_test_threads) { // Even though we use an atomic, mutex must be held during // modification. See documentation of condition variable // notify_all/wait. Mutex does not need to be held for the actual // notify call. std::lock_guard<std::mutex> lk(someclass->cv_m); someclass->pause_test_threads = true; } } void unpause_test_threads(SomeClass *someclass) { if (someclass->pause_test_threads) { { // Even though we use an atomic, mutex must be held during // modification. See documentation of condition variable // notify_all/wait. Mutex does not need to be held for the actual // notify call. std::lock_guard<std::mutex> lk(someclass->cv_m); someclass->pause_test_threads = false; } someclass->cv.notify_all(); // Allow send/receive threads to run. } } void wait_to_start(SomeClass *someclass) { std::unique_lock<std::mutex> lk(someclass->cv_m); // RAII, no need for unlock. auto not_paused = [someclass](){return someclass->pause_test_threads == false;}; someclass->cv.wait(lk, not_paused); } void ctrl_thread(SomeClass *someclass) { // Do startup work // ... unpause_test_threads(someclass); for (;;) { // ... check for end-program etc, if so, break; if (lost ctrl connection to other endpoint) { pause_test_threads(); } else { unpause_test_threads(); } sleep(SLEEP_INTERVAL); } unpause_test_threads(someclass); } void sender_thread(SomeClass *someclass) { wait_to_start(someclass); ... for (;;) { // ... check for end-program etc, if so, break; if (someclass->pause_test_threads) wait_to_start(someclass); ... } } void receiver_thread(SomeClass *someclass) { wait_to_start(someclass); ... for (;;) { // ... check for end-program etc, if so, break; if (someclass->pause_test_threads) wait_to_start(someclass); ... } 
+5
source share
3 answers

I looked at your code that controls a conditional variable and atomic, and it seems that it is correct and will not cause problems.

Why should you protect the entry in a shared variable, even if it is atomic:

There may be problems if writing to a shared variable occurs between the check in the predicate and the wait on the condition. Consider the following:

  • Waiting for a thread wakes up falsely, uses mutexes, checks the predicate and evaluates it to false , so it must wait for cv again.

  • Flow control sets the shared variable to true .

  • Flow control sends a notification that is not received by anyone because there are no threads waiting for the condition variable.

  • A thread is waiting for a conditional variable. Since the notification has already been sent, it will wait for the next side wake-up or the next time the flow control sends a notification. Potentially, the expectation is vague.

Reading from shared atomic variables without locking is generally safe unless it introduces TOCTOU problems .

In your case, you read the shared variable to avoid unnecessary blocking, and then check it again after blocking (in a conditional wait call). This is a valid optimization called double check for blocking, and I do not see any potential problems here.

You might want to check if atomic<bool> free. Otherwise, you will have even more locks that you would have had without it.

+6
source

In general, you want to consider the fact that an atom variable is independent of how it works with the condition variable.

If all the code that interacts with the condition variable follows the normal mutex lock pattern before the request / modification, and the code that interacts with the condition variable does not rely on the code that does not interact with the condition variable, it will continue to be true, even if it atomic mutex wraps.

From a quick read of your pseudo code, this looks right. However, pseudo code is often a poor substitute for real code for multi-threaded code.

"Optimization" is only the expectation of a variable condition (and mutex lock), when reading an atom says that you may or may not be optimized. You need to skip the bandwidth.

+1
source

atomic data do not need another synchronization, it is the basis of blocking algorithms and data structures.

 void do_pause_test_threads(SomeClass *someclass) { if (!someclass->pause_test_threads) { /// your pause_test_threads might be changed here by other thread /// so you have to acquire mutex before checking and changing /// or use atomic methods - compare_exchange_weak/strong, /// but not all together std::lock_guard<std::mutex> lk(someclass->cv_m); someclass->pause_test_threads = true; } } 
0
source

Source: https://habr.com/ru/post/1245467/


All Articles