Search code examples
c++windowsmultithreadingcondition-variable

std::thread not waking up when using notify_one() function even with predicate


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.

Console picture

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!


Solution

  • 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.