In C++, I am trying to get a handle for the publisher/consumer pattern using a condition_variable. This is loosely the template I have seen online:
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <chrono>
using namespace std;
mutex m;
queue<string> que;
condition_variable cond;
void write(string &&msg) {
unique_lock<mutex> locker(m);
que.push(msg);
locker.unlock();
cond.notify_one();
this_thread::sleep_for(chrono::milliseconds(1));
}
void read() {
while (true) {
unique_lock<mutex> locker(m);
cond.wait(locker);
if (!que.empty()) {
cout << que.front() << endl;
que.pop();
}
locker.unlock();
}
}
void publisher(string &msg) {
for (int i = 0; i < 100; ++i)
write("Publisher: " + msg + ", " + to_string(i));
}
int main() {
string msg = "Hello";
thread pub_thread(publisher, std::ref(msg));
/* The main thread will print the publisher's messages to cout. */
read();
/* Make the main thread wait for the publisher to finish. */
pub_thread.join(); //
return 0;
}
The thing I do not understand is the sleep_for call on the publisher thread. I understand that this is there simply to simulate a "real life" scenario in which the publisher will not be spitting out messages so fast. However, curiously, if I comment out that line, the code does not run to completion. Why is that?
Furthermore, I tried setting the sleep_for time to 0 with the same effect. It seems that the published fundamentally needs to sleep, but I do not understand why. To get more specific, the code should print out 100 messages. If I let the code sleep for 1 ms, then all 100 messages are printed. If I do not, then I only see about 10 messages before the code freezes. It seems like there is a deadlock occurring.
Bonus points if there is a better pattern which avoids having to put the publisher to sleep...
I am aware that in practice you need to have a strategy for stopping the main thread, like a poison pill. I have intentionally omitted this to focus on the present discussion.
EDIT
Hmmm. If I put in a block to handle spurious wakeup, then that fixes the problem. But that still does not explain why the original code failed.
Here is an improved read function:
void read() {
while (true) {
unique_lock<mutex> locker(m);
cond.wait(locker, [&](){ return !que.empty(); });
cout << que.front() << endl;
que.pop();
locker.unlock();
}
}
You need to handle the situation when the publisher is publishing faster than the consumer consuming.
When that happens, the consumer will miss condition_variable triggers. Remember that notify calls do not accumulate.
Change the consumer to consume all available messages once it wakes up:
if (!que.empty()) → while (!que.empty())
Like this:
void read() {
while (true) {
unique_lock<mutex> locker(m);
cond.wait(locker);
while (!que.empty()) {
cout << que.front() << endl;
que.pop();
}
locker.unlock();
}
}
You have two problems: one is that the publisher is under no obligation to yield to the reader - or to pause long enough for the reader to successfully get lock the mutex - unless you make it.
The second is that your reader is anyway incorrect:
void read() {
while (true) {
unique_lock<mutex> locker(m);
cond.wait(locker);
if (!que.empty()) {
cout << que.front() << endl;
que.pop();
}
}
}
This assumes you get one "wakeup event" per element pushed onto the queue, because it consumes exactly one element per wakeup. But that's not how condition variables work.
It's entirely possible for this sequence to happen:
publisher adds item 1
publisher signals condvar
publisher adds item 2
publisher signals condvar
reader wakes from condvar wait
reader consumes item 1
In this case the publisher will add 100 items, and signal 100 times, but the reader will only wake 99 times and therefore consume at most 99 items.
The correct code should be something like this:
void read() {
unique_lock<mutex> locker(m);
while (true) {
// don't wait if we don't have to
while (que.empty()) {
cond.wait(locker);
}
// consume everything we can
while (!que.empty()) {
cout << que.front() << endl;
que.pop();
}
}
}
Using the predicate achieves roughly the same thing (I just wrote all the logic out explicitly for clarity) - the second while isn't there in your edit, but looping round and skipping the first while is a slightly more expensive way to get the same behaviour.
Also, there's no need to thrash the mutex on every iteration - the condition variable already (un)locks it as required.
Related
I'm studying concurrency in C++ and I'm trying to implement a multithreaded callback registration system. I came up with the following code, which is supposed to accept registration requests until an event occurs. After that, it should execute all the registered callbacks in order with which they were registered. The registration order doesn't have to be deterministic.
The code doesn't work as expected. First of all, it rarely prints the "Pushing callback with id" message. Secondly, it sometimes hangs (a deadlock caused by a race condition, I assume). I'd appreciate help in figuring out what's going on here. If you see that I overcomplicate some parts of the code or misuse some pieces, please also point it out.
#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
class CallbackRegistrar{
public:
void registerCallbackAndExecute(std::function<void()> callback) {
if (!eventTriggered) {
std::unique_lock<std::mutex> lock(callbackMutex);
auto saved_id = callback_id;
std::cout << "Pushing callback with id " << saved_id << std::endl;
registeredCallbacks.push(std::make_pair(callback_id, callback));
++callback_id;
callbackCond.wait(lock, [this, saved_id]{return releasedCallback.first == saved_id;});
releasedCallback.second();
callbackExecuted = true;
eventCond.notify_one();
}
else {
callback();
}
}
void registerEvent() {
eventTriggered = true;
while (!registeredCallbacks.empty()) {
releasedCallback = registeredCallbacks.front();
callbackCond.notify_all();
std::unique_lock<std::mutex> lock(eventMutex);
eventCond.wait(lock, [this]{return callbackExecuted;});
callbackExecuted = false;
registeredCallbacks.pop();
}
}
private:
std::queue<std::pair<unsigned, std::function<void()>>> registeredCallbacks;
bool eventTriggered{false};
bool callbackExecuted{false};
std::mutex callbackMutex;
std::mutex eventMutex;
std::condition_variable callbackCond;
std::condition_variable eventCond;
unsigned callback_id{1};
std::pair<unsigned, std::function<void()>> releasedCallback;
};
int main()
{
CallbackRegistrar registrar;
std::thread t1(&CallbackRegistrar::registerCallbackAndExecute, std::ref(registrar), []{std::cout << "First!\n";});
std::thread t2(&CallbackRegistrar::registerCallbackAndExecute, std::ref(registrar), []{std::cout << "Second!\n";});
registrar.registerEvent();
t1.join();
t2.join();
return 0;
}
This answer has been edited in response to more information being provided by the OP in a comment, the edit is at the bottom of the answer.
Along with the excellent suggestions in the comments, the main problem that I have found in your code is with the callbackCond condition variable wait condition that you have set up. What happens if releasedCallback.first does not equal savedId?
When I have run your code (with a thread-safe queue and eventTriggered as an atomic) I found that the problem was in this wait function, if you put a print statement in that function you will find that you get something like this:
releasedCallback.first: 0, savedId: 1
This then waits forever.
In fact, I've found that the condition variables used in your code aren't actually needed. You only need one, and it can live inside the thread-safe queue that you are going to build after some searching ;)
After you have the thread-safe queue, the code from above can be reduced to:
class CallbackRegistrar{
public:
using NumberedCallback = std::pair<unsigned int, std::function<void()>>;
void postCallback(std::function<void()> callback) {
if (!eventTriggered)
{
std::unique_lock<std::mutex> lock(mutex);
auto saved_id = callback_id;
std::cout << "Pushing callback with id " << saved_id << std::endl;
registeredCallbacks.push(std::make_pair(callback_id, callback));
++callback_id;
}
else
{
while (!registeredCallbacks.empty())
{
NumberedCallback releasedCallback;
registeredCallbacks.waitAndPop(releasedCallback);
releasedCallback.second();
}
callback();
}
}
void registerEvent() {
eventTriggered = true;
}
private:
ThreadSafeQueue<NumberedCallback> registeredCallbacks;
std::atomic<bool> eventTriggered{false};
std::mutex mutex;
unsigned int callback_id{1};
};
int main()
{
CallbackRegistrar registrar;
std::vector<std::thread> threads;
for (int i = 0; i < 10; i++)
{
threads.push_back(std::thread(&CallbackRegistrar::postCallback,
std::ref(registrar),
[i]{std::cout << std::to_string(i) <<"\n";}
));
}
registrar.registerEvent();
for (auto& thread : threads)
{
thread.join();
}
return 0;
}
I'm not sure if this does exactly what you want, but it doesn't deadlock. It's a good starting point in any case, but you need to bring your own implementation of ThreadSafeQueue.
Edit
This edit is in response to the comment by the OP stating that "once the event occurs, all the callbacks should be executed in [the] order that they've been pushed to the queue and by the same thread that registered them".
This was not mentioned in the original question post. However, if that is the required behaviour then we need to have a condition variable wait in the postCallback method. I think this is also the reason why the OP had the condition variable in the postCallback method in the first place.
In the code below I have made a few edits to the callbacks, they now take input parameters. I did this to print some useful information while the code is running so that it is easier to see how it works, and, importantly how the condition variable wait is working.
The basic idea is similar to what you had done, I've just trimmed out the stuff you didn't need.
class CallbackRegistrar{
public:
using NumberedCallback = std::pair<unsigned int, std::function<void(int, int)>>;
void postCallback(std::function<void(int, int)> callback, int threadId) {
if (!m_eventTriggered)
{
// Lock the m_mutex
std::unique_lock<std::mutex> lock(m_mutex);
// Save the current callback ID and push the callback to the queue
auto savedId = m_currentCallbackId++;
std::cout << "Pushing callback with ID " << savedId << "\n";
m_registeredCallbacks.push(std::make_pair(savedId, callback));
// Wait until our thread's callback is next in the queue,
// this will occur when the ID of the last called callback is one less than our saved callback.
m_conditionVariable.wait(lock, [this, savedId, threadId] () -> bool
{
std::cout << "Waiting on thread " << threadId << " last: " << m_lastCalledCallbackId << ", saved - 1: " << (savedId - 1) << "\n";
return (m_lastCalledCallbackId == (savedId - 1));
});
// Once we are finished waiting, get the callback out of the queue
NumberedCallback retrievedCallback;
m_registeredCallbacks.waitAndPop(retrievedCallback);
// Update last callback ID and call the callback
m_lastCalledCallbackId = retrievedCallback.first;
retrievedCallback.second(m_lastCalledCallbackId, threadId);
// Notify one waiting thread
m_conditionVariable.notify_one();
}
else
{
// If the event is already triggered, call the callback straight away
callback(-1, threadId);
}
}
void registerEvent() {
// This is all we have to do here.
m_eventTriggered = true;
}
private:
ThreadSafeQueue<NumberedCallback> m_registeredCallbacks;
std::atomic<bool> m_eventTriggered{ false};
std::mutex m_mutex;
std::condition_variable m_conditionVariable;
unsigned int m_currentCallbackId{ 1};
std::atomic<unsigned int> m_lastCalledCallbackId{ 0};
};
The main function is as above, except I am creating 100 threads instead of 10, and I have made the callback print out information about how it was called.
for (int createdThreadId = 0; createdThreadId < 100; createdThreadId++)
{
threads.push_back(std::thread(&CallbackRegistrar::postCallback,
std::ref(registrar),
[createdThreadId](int registeredCallbackId, int callingThreadId)
{
if (registeredCallbackId < 0)
{
std::cout << "Callback " << createdThreadId;
std::cout << " called immediately, from thread: " << callingThreadId << "\n";
}
else
{
std::cout << "Callback " << createdThreadId;
std::cout << " called from thread " << callingThreadId;
std::cout << " after being registered as " << registeredCallbackId << "\n";
}
},
createdThreadId));
}
I am not entirely sure why you want to do this, as it seems to defeat the point of having multiple threads, although I may be missing something there. But, regardless, I hope this helps you to understand better the problem you are trying to solve.
Experimenting with this code some more, I found out why the "Pushing callback with id " part was rarely printed. It's because the call to registrar.registerEvent from the main thread was usually faster than the calls to registerCallbackAndExecute from separate threads. Because of that, the condition if (!eventTriggered) was almost never fulfilled (eventTriggered had been set to true in the registerEvent method) and hence all calls to registerCallbackAndExecute were falling into the else branch and executing straightaway.
Then, the program sometimes also didn't finish, because of a race condition between registerEvent and registerCallbackAndExecute. Sometimes, registerEvent was being called after the check if (!eventTriggered) but before pushing the callback to the queue. Then, registerEvent completed instantly (as the queue was empty) while the thread calling registerCallbackAndExecute was pushing the callback to the queue. The latter thread then kept waiting forever for the event (that had already happened) to happen.
I've been trying to write both periodic and non-periodic messages on a serial port using Windows API. The architecture of my code is the following:
The main thread starts the following :
A "message sending thread" that will be responsible for writing data over the serial port. It will wait on a condition variable to be waken up by other threads, then will send data contained in a buffer over the serial port. I'm protected from Spurious wake-ups by a flag.
A thread that builds a message, fill the data buffer and notifies the "message sending thread" with the notify_one() function every 100ms.
For debug purpose, the main thread also loops a few times on another function that builds a message and notifies the "message sending thread" the same way as the timer do every 500 ms. Here's the code
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <Windows.h>
std::condition_variable m_cvSend;
std::mutex m_mtxSend;
std::thread m_threadSendMessage;
std::thread m_threadPeriodicMessage;
char messageToSend[512] = { 0 };
bool newMessage = false;
bool threadRunning = false;
int count = 0;
void SendPeriodicMessage();
void SendFoo();
void sendMessageRun(void);
int main()
{
threadRunning = true;
//Start message sending thread
m_threadSendMessage = std::thread(&sendMessageRun);
m_threadPeriodicMessage = std::thread(&SendPeriodicMessage);
Sleep(1000);
while (count < 20) {
SendFoo();
Sleep(500);
count++;
}
m_threadSendMessage.join();
}
void sendMessageRun(void) {
std::unique_lock<std::mutex> lck(m_mtxSend);
DWORD bytesEcrits = 0;
while (threadRunning == true) {
m_cvSend.wait(lck, [&] {return newMessage; });
std::cout << "I'm HERE" << std::endl;
std::cout << messageToSend << std::endl;
//Send message over Serial port
//NOT RELEVANT FOR THE ISSUE
//Clean buffer
Sleep(20);
memset(messageToSend, 0, sizeof(messageToSend));
bytesEcrits = 0;
newMessage = 0;
}
}
//Envoi d'une consigne de pointage
void SendPeriodicMessage() {
while (true) {
//Send every 100 ms
Sleep(100);
std::lock_guard<std::mutex> lkEnvoi(m_mtxSend);
strcpy_s(messageToSend, "Thread 1");
//Flag for spurious wake ups
newMessage = true;
//// Debug
//std::cout << "Message to be sent periodically" << std::endl;
//std::cout << messageToSend << std::endl;
//End of critical section
//Notify sending thread
m_cvSend.notify_one();
}
}
void SendFoo() {
std::lock_guard<std::mutex> lkEnvoi(m_mtxSend);
char countChar[3] = { 0 };
_itoa_s(count, countChar, 10);
strcpy_s(messageToSend, "foo");
strcat_s(messageToSend, countChar);
//Flag for spurious wake ups
newMessage = true;
//End of critical section
//Notify sending thread
m_cvSend.notify_one();
}
While running this, I see that the foo function sometimes doesn't wake up the thread. In fact, the function should notify the thread to wake up through the notify_one() function. However, I suppose the wait() function doesn't unlock as I don't observe another "I'M HERE" on the console.
I've seen that waking up a thread with a notify_one() is done atomically so I don't understand why it wouldn't be done with something in between interfering.
I've tried changing the thread priorities using Windows API but it doesn't work.
Some help would be highly appreciated for this first post!
Thank you!
You have two different threads which can "send" a message. Neither of those threads whether theres already a pending message (e.g. newMessage==true;).
notify_one will eventually notify the receiving thread, but there's nothing that guarantees it does so immediately.
Add an assert(! newMessage); in both of your send functions, and you'll presumably see one being hit.
I am trying to create a sort of threadpool that runs functions on separate threads and only starts a new iteration when all functions have finished.
map<size_t, bool> status_map;
vector<thread> threads;
condition_variable cond;
bool are_all_ready() {
mutex m;
unique_lock<mutex> lock(m);
for (const auto& [_, status] : status_map) {
if (!status) {
return false;
}
}
return true;
}
void do_little_work(size_t id) {
this_thread::sleep_for(chrono::seconds(1));
cout << id << " did little work..." << endl;
}
void do_some_work(size_t id) {
this_thread::sleep_for(chrono::seconds(2));
cout << id << " did some work..." << endl;
}
void do_much_work(size_t id) {
this_thread::sleep_for(chrono::seconds(4));
cout << id << " did much work..." << endl;
}
void run(const function<void(size_t)>& function, size_t id) {
while (true) {
mutex m;
unique_lock<mutex> lock(m);
cond.wait(lock, are_all_ready);
status_map[id] = false;
cond.notify_all();
function(id);
status_map[id] = true;
cond.notify_all();
}
}
int main() {
threads.push_back(thread(run, do_little_work, 0));
threads.push_back(thread(run, do_some_work, 1));
threads.push_back(thread(run, do_much_work, 2));
for (auto& thread : threads) {
thread.join();
}
return EXIT_SUCCESS;
}
I expect to get the output:
0 did little work...
1 did some work...
2 did much work...
0 did little work...
1 did some work...
2 did much work...
.
.
.
after the respective timeouts but when I run the program I only get
0 did little work...
0 did little work...
.
.
.
I also have to say that Im rather new to multithreading but in my understanding, the condition_variable should to the taks of blocking every thread till the predicate returns true. And in my case are_all_ready should return true after all functions have returned.
There are several ways to do this.
Easiest in my opinion would be a C++20 std::barrier, which says, "wait until all of N threads have arrived and are waiting here."
#include <barrier>
std::barrier synch_workers(3);
....
void run(const std::function<void(size_t)>& func, size_t id) {
while (true) {
synch_workers.arrive_and_wait(); // wait for all three to be ready
func(id);
}
}
Cruder and less efficient, but equally effective, would be to construct and join() new sets of three worker threads for each "batch" of work:
int main(...) {
std::vector<thread> threads;
...
while (flag_running) {
threads.push_back(...);
threads.push_back(...);
...
for (auto& thread : threads) {
thread.join();
}
threads.clear();
}
Aside
I'd suggest you revisit some core synchronization concepts, however. You are using new mutexes when you want to re-use a shared one. The scope of your unique_lock isn't quite right.
Now, your idea to track worker thread "busy/idle" state in a map is straightforward, but cannot correctly coordinate "batches" or "rounds" of work that must be begun at the same time.
If a worker sees in the map that two of three threads, including itself, are "idle", what does that mean? Is a "batch" of work concluding — i.e., two workers are waiting for a tardy third? Or has a batch just begun — i.e., the two idle threads are tardy and had better get to work like their more eager peer?
The threads cannot know the answer without keeping track of the current batch of work, which is what a barrier (or its more complex cousin the phaser) does under the hood.
As-is, your program has a crash (UB) due to concurrent access to status_map.
When you do:
void run(const function<void(size_t)>& function, size_t id)
{
...
mutex m;
unique_lock<mutex> lock(m);
...
status_map[id] = false;
the locks created are local variables, one per thread, and as such independent. So, it doesn't prevent multiple threads from writing to status_map at once, and thus crashing. That's what I get on my machine.
Now, if you make the mutex static, only one thread can access the map at once. But that also makes it so that only one thread runs at once. With this I see 0, 1 and 2 running, but only once at a time and a strong tendency for the previous thread to have run to run again.
My suggestion, go back to the drawing board and make it simpler. All threads run at once, single mutex to protect the map, only lock the mutex to access the map, and ... well, in fact, I don't even see the need for a condition variable.
e.g. what is wrong with:
#include <thread>
#include <iostream>
#include <vector>
using namespace std;
vector<thread> threads;
void do_little_work(size_t id) {
this_thread::sleep_for(chrono::seconds(1));
cout << id << " did little work..." << endl;
}
void do_some_work(size_t id) {
this_thread::sleep_for(chrono::seconds(2));
cout << id << " did some work..." << endl;
}
void do_much_work(size_t id) {
this_thread::sleep_for(chrono::seconds(4));
cout << id << " did much work..." << endl;
}
void run(const function<void(size_t)>& function, size_t id) {
while (true) {
function(id);
}
}
int main() {
threads.push_back(thread(run, do_little_work, 0));
threads.push_back(thread(run, do_some_work, 1));
threads.push_back(thread(run, do_much_work, 2));
for (auto& thread : threads) {
thread.join();
}
return EXIT_SUCCESS;
}
I need to run an activity every so often while my program is running. In production code this is configurable with a default of 30 minutes, but in the example below I've used 5 seconds. Previously I had a std::thread that would loop once per second checking to see if it was time to run the activity OR if the program was closed. This allowed me to close the program at any time without having the .join() on the activity's thread block my application's exit waiting for its next iteration. At any moment it was less than a second away from checking to see if it should close or perform the activity.
I do not like the idea of wasting time checking every second for an activity that may only occur every 30 minutes while the program is running, so I attempted to switch it to a condition variable. I've included a small example of my implementation below. I want to be sure I'm using the right tools to do this. The issue I see with my code is unnecessary calls of the lambda expression which I'll explain below.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
bool asking_thread_to_quit;
std::mutex cv_mutex;
std::condition_variable cv;
void RunThread()
{
{
std::lock_guard<std::mutex> lock(cv_mutex);
asking_thread_to_quit = false;
}
std::cout << "Started RunThread." << std::endl;
while(true)
{
{
std::unique_lock<std::mutex> lock(cv_mutex);
std::chrono::seconds delay(5);
if(cv.wait_for(lock, delay, [] { std::cout << "WAKEUP" << std::endl; return asking_thread_to_quit; })) // timed out
{
std::cout << "Breaking RunThread Loop." << std::endl;
break;
}
}
std::cout << "TIMER CODE!" << std::endl;
}
}
int main(int argc, char *argv[])
{
std::cout << "Program Started" << std::endl;
std::thread run_thread(RunThread);
// This is where the rest of the program would be implemented, but for the sake of this example, simply wait for user input to allow the thread to run in the background:
char test;
std::cin >> test;
{
std::lock_guard<std::mutex> lock(cv_mutex);
asking_thread_to_quit = true;
}
cv.notify_all();
std::cout << "Joining RunThread..." << std::endl;
run_thread.join();
std::cout << "RunThread Joined." << std::endl;
return 0;
}
If you execute the program and allow for one 5-second iteration to pass, it gives the following output:
Program Started
Started RunThread.
WAKEUP
WAKEUP
TIMER CODE!
WAKEUP
q <-- I typed this to quit.
Joining RunThread...
WAKEUP
Breaking RunThread Loop.
RunThread Joined.
You can see that it does the following:
(WAKEUP) Performs the check prior to waiting
Wait for five seconds
(WAKEUP) Performs the check
(TIMER CODE!) Executes the activity
(WAKEUP) Performs the check again before going back to waiting
Step 5 seems unnecessary as I just performed it a split second ago, but I believe it is necessary as .wait_for() doesn't know I'm using it inside of a while(true) loop. Is this something I'm stuck with, or is there a way to remove the initial check in the .wait_for() call? I'm guessing there is not as it would allow for the system to .wait_for() something that it doesn't need to wait for. This is what leads me to wonder if I'm using the right language features to begin with. Is there a better way?
The Answer
The answer given below goes into detail on other issues with my code as well as sparked an informative related conversation. I'm going to accept that answer as it helped me the most, but the quick answer to the question seems to be this:
asking_thread_to_quit could have been set to true during the TIMER CODE! section, requiring another check prior to waiting on the condition variable again.
Your code has a few issues with it.
void RunThread()
{
asking_thread_to_quit = false;
This is a race condition. You shouldn't modify a non-atomic shared variable in two different threads without synchronization.
std::cout << "Started RunThread." << std::endl;
while(true)
{
std::unique_lock<std::mutex> lock(cv_mutex);
std::chrono::seconds delay(5);
First using namespace std::literals::chrono_literals;. Then use 5s.
if(cv.wait_for(lock, delay, [] { std::cout << "WAKEUP" << std::endl; return asking_thread_to_quit; })) // timed out
{
std::cout << "Breaking RunThread Loop." << std::endl;
break;
}
else
{
std::cout << "TIMER CODE!" << std::endl;
}
the TIMER CODE usually shouldn't run within the std::mutex lock, as that means anyone sending a message is blocked until the timer code is finished.
}
}
Finally, WAKEUPs are spurious details. You could WAKEUP 50 times in that 5 seconds; condition variables do not guarantee a bounded number of checks.
asking_thread_to_quit = true;
cv.notify_all();
this again results in a race condition; your program does undefined behavior twice over now.
Changing asking_thread_to_quit to a std::atomic<bool> will get rid of the formal race condition and UB. It will, however, let your code miss a request to quit and mistakenly do another 5 second sleep followed by the task.
This is because the return value of your lambda could be calculated, then the asking_thread_to_quit=true and notify_all evaluates with nothing waiting on the condition variable (so nothing is woken up), then the condition variable is blocked on, 5 seconds pass, it wakes up returning false, then repeats the while loop.
With the mutex being held in all writes to the bool, the write cannot occur until after the lambda has returned and we are waiting on the condition with an unlocked mutex. This prevents the .notify_all() from being missed.
The cargo-cult solution to this is to always guard all reads and writes to asking_thread_to_quit by the cv_mutex. Then avoid holding the cv_mutex for any length of time, including while handling the timer wakeup.
std::unique_lock<std::mutex> lock_cv() {
return std::unique_lock<std::mutex>(cv_mutex);
}
void RunThread()
{
{
auto lock = lock_cv();
asking_thread_to_quit = false;
}
std::cout << "Started RunThread." << std::endl;
while(true)
{
{
auto lock = lock_cv();
using namespace std::literals::chrono_literals;
if(cv.wait_for(lock, 5s, [] { std::cout << "WAKEUP" << std::endl; return asking_thread_to_quit; })) // timed out
{
std::cout << "Breaking RunThread Loop." << std::endl;
break;
}
}
std::cout << "TIMER CODE!" << std::endl;
}
}
and in main:
{
auto lock = lock_cv();
asking_thread_to_quit = true;
}
cv.notify_all();
And yes, I intended for cv.notify_all() to be outside the mutex. It works; understanding why is outside the scope of the "cargo-cult" solution I'm providing here.
Finally, the WAKEUP is not spurious. The asking_thread_to_quit could have changed since the last time it was checked. Running the lambda guarantees we should fall asleep in a careful manner, with no gap between unlocking the mutex for waiting and waiting for notifications.
Spurious WAKEUPs can still occur; they would show up as more WAKEUPs than you expect.
I'm trying to run a thread with a function from a class member and use conditional variable to wait until the main thread signals and add the times the thread got signaled. Here is the code:
// Example program
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
#include <unistd.h>
#include <mutex>
#include <condition_variable>
std::mutex m_mutex;
std::condition_variable m_condVar;
char stop =0;
class dummclass
{
std::thread dummclass_thread;
int alarms;
public:
dummclass() :
alarms(0),
dummclass_thread(std::thread(&dummclass::dummclassThreadProc, this))
{
}
~dummclass()
{
std::cout<<"Alarms: "<<alarms<<"\n";
//signal thread before joining
{
std::lock_guard<std::mutex> lock_guard(m_mutex);
stop=1;
}
m_condVar.notify_one();
dummclass_thread.join();
}
private:
void dummclassThreadProc()
{
{
std::unique_lock<std::mutex> mlock(m_mutex);
std::cout<<"thread waiting\n";
m_condVar.wait(mlock);
std::cout<<"thread done waiting\n";
}
sleep(1);
std::unique_lock<std::mutex> mlock(m_mutex);
while (!stop)//!stop_dummclass.load())
{
std::cout<<"got mutex\n";
m_condVar.wait(mlock);
std::cout<<"wait done\n";
{
std::cout<<"got one\n";
alarms++;
}
}
std::cout<<"end loop\n";
}
};
int main()
{
dummclass *x = new dummclass;
sleep(3);
{
std::lock_guard<std::mutex> lock_guard(m_mutex);
}
m_condVar.notify_one();
std::cout<<"done waiting\n";
sleep(3);
for(int i=0;i<13;i++)
{
{
std::cout<<"signal "<<i<<"\n";
std::lock_guard<std::mutex> lock_guard(m_mutex);
}
m_condVar.notify_one();
}
delete x;
}
The weird part is that the initial waiting and signaling that are outside of the loops actually work ok. I don't understand what mistake I do so that the while loop inside the class thread doesn't catch any signal from the main thread but it catches a signal from the destructor of the dummyclass when I delete it. This is the output:
thread waiting
done waiting
thread done waiting
got mutex
signal 0 signal 1 signal 2 signal 3 signal 4 signal 5 signal 6 signal 7 signal
8 signal 9 signal 10 signal 11 signal 12
Alarms: 0
wait done
got one end loop
EDIT: It seems that adding a 1 second sleep in the main() for loop solves the problem. Is it possible that the for loop gets the mutex before wait() manages to wake and lock the mutex ?
for(int i=0;i<13;i++)
{
{std::cout<<"signal "<<i<<"\n";
std::lock_guard<std::mutex> lock_guard(m_mutex);}
m_condVar.notify_one();
sleep(1);
}
Can someone please show me what is wrong ?
Thanks.
The object doing the waiting gets deleted before it processes the signal. Since the delete happens on a known to be running thread it has a fair chance to get executed first. In particular it is also likely to reacquire the lock again: Since the notify_one() is done while the mutex is locked the wait()ing thread cannot acquire it and will go back to sleep, waiting for the mutex to be released. That gives the signalling thread an opportunity to reacquire the lock. The only forced synchronizqtion causing the signalling thread to wait is the join() and it does give the waiting thread a chance to execute.
Note that signals of condition variables are not something delivered to the waiting thread. They are essentially wake-up calls. The waiting thread will wake up eventually once a signal is delivered. However, many signals can be delivered before it actually does so.
I don't understand what mistake I do so that the while loop inside the
class thread doesn't catch any signal from the main thread
Even though multiple notifications are sent the thread may only receive a single notification.
The notify_one() call does
not mean that the current thread will stop and wait for another thread.
It just means that the other thread must wake up at some point because something may have happened that it would be interested in.
Also note that std::condition_variable::wait could experience a spurious wakeup, so it might not even have anything to do or have received a 'real' signal.
The solution is to provide a predicate as a parameter to the wait() call. The predicate can then check if there is a signal (via a variable provided for this purpose and only changed under lock) and may also check if the program has been stopped.
In the updated program below I've added a predicate to the wait and made some minor changes. The program only notifies under lock, but you might choose not to.
// Example program - modified
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
//#include <unistd.h>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::mutex m_mutex;
std::condition_variable m_condVar;
bool signal_waiting{false};
bool stop{false};
class dummclass
{
int alarms{};
std::thread dummclass_thread{[this](){dummclassThreadProc(); }};
public:
~dummclass()
{
std::cout << "Alarms: " << alarms << "\n";
//signal thread before joining
{
std::lock_guard<std::mutex> lock_guard(m_mutex);
stop = 1;
m_condVar.notify_one();
}
dummclass_thread.join();
}
private:
void dummclassThreadProc()
{
{
std::unique_lock<std::mutex> mlock(m_mutex);
std::cout << "thread waiting\n";
m_condVar.wait(mlock);
std::cout << "thread done waiting\n";
}
std::this_thread::sleep_for(std::chrono::seconds{1});
while(!stop)//!stop_dummclass.load())
{
std::unique_lock<std::mutex> mlock(m_mutex);
std::cout << "got mutex\n";
//m_condVar.wait(mlock);
m_condVar.wait(mlock, [](){return signal_waiting || stop; });
if(stop)
break;
std::cout << "wait done\n";
std::cout << "got one\n";
alarms++;
signal_waiting = false;
m_condVar.notify_one();
}
std::cout << "end loop\n";
}
};
int main()
{
dummclass *x = new dummclass;
//sleep(3);
std::this_thread::sleep_for(std::chrono::seconds{1});
{
std::lock_guard<std::mutex> lock_guard(m_mutex);
m_condVar.notify_one();
}
std::cout << "done waiting\n";
//sleep(3);
std::this_thread::sleep_for(std::chrono::seconds{1});
for(int i = 0; i<13; i++)
{
{
std::cout << "signal " << i << "\n";
std::unique_lock<std::mutex> lock(m_mutex);
m_condVar.wait(lock, [](){return !signal_waiting ; });
signal_waiting = true;
m_condVar.notify_one();
}
}
delete x;
}