std::condition_variable not working with std::this_thread::sleep_for()

Jasper1378

Could someone explain why this code does not work as expected when the sleep call in producer() is uncommented? It works fine if the sleep call is moved out of the scope of the unique_lock.

To clarify: Working as expected: producer() creates new messages that are stored in data those messages are then printed by consumer(). What happens when the sleep call is uncommented: no output is produced. I would expect new messages to be produced and printed at 1 second intervals.

#include <atomic>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <ratio>
#include <string>
#include <thread>
#include <queue>

std::string data;
std::atomic<bool> ready;
std::condition_variable cv;
std::mutex m;

void producer()
{
    int c{};
    while (true)
    {
        ++c;
        {
            std::unique_lock<std::mutex> lock{ m };
            data = "count: " + std::to_string(c);
            // std::this_thread::sleep_for(std::chrono::seconds{ 1 }); // THIS LINE
            ready = true;
        }
        cv.notify_one();
    }
}

void consumer()
{
    while (true)
    {
        std::unique_lock<std::mutex> lock{ m };
        cv.wait(lock, []{ return ready == true; });
        std::cout << data << '\n';
        ready = false;
    }
}

int main()
{
    std::thread tp{ producer };
    std::thread tc{ consumer };

    tp.join();
    tc.join();
}
selbie

Each iteration of the while loop in the producer thread takes a lock, computes data, sets ready=true, and then unlocks before repeating.

In the case of the sleep statement being uncommented out:

Since the mutex is still held by the OS while sleep is invoked, both the producer and consumer thread are effectively paused and are not being given any quantum to run.

Then the sleep statement completes. This enables the producer thread to wake up (with a full quantum). The producer thread, within the quantum time likely is attempting to do the following:

  1. set ready=true
  2. unlocks the mutex as a result of the scope completing
  3. invokes notify_one to signal the consumer thread that it can get a shot. But keep in mind, the consumer thread is likely paused at this point while the producer thread is running.
  4. re-acquire thes lock.

Meanwhile the consumer thread is blocked on the wait call. But it eventually gets signaled as available to run when the notify_one call is invoked by the producer thread. Then this likely happens:

The consumer thread will get woken up during step 3 of the producer thread above. The thread's register state and other stuff will get restored by the OS. This takes a few cycles. Then it attempts to acquire the lock itself. But by the time it trys to acquire the lock, the producer thread has grabbed it again, because it's already running and not blocked on anything.

Now, I ran your program with the sleep statement uncommented. It will occassionally print a count: XX line, but rarely. There's a small window in which the consumer thread can "win" the race with the producer thread and acquire the mutex. But it will take many iterations and some luck. In my local repro of building and running your original code with the sleep statement uncommented, the program printed this line exactly and nothing else after running for several minutes.

count: 41

It just happens to work when the sleep statement is commented out because the thread scheduler just happens to occasionally context switch between threads in between unlock and lock calls to the mutex. You might even have different behavior depending on the number of available cores on your system. As evidence of this, you can see that the consumer thread winds up skipping over a bunch of values on each consecutive print call. That's because the producer thread isn't ever blocked on i/o like the consumer thread is with the cout and wait statements.

count: 131
count: 134
count: 2661
count: 2804
count: 2880
...

I'm guessing you want to have the consumer thread print each new value assigned to data. If that's the case, unlock the mutex while sleeping. We can unlock and relock the mutex between sleep statements. But just swapping some lines will suffice:

void producer()
{
    int c{};
    while (true)
    {
        ++c;
        {
            std::unique_lock<std::mutex> lock{ m };
            data = "count: " + std::to_string(c);
            ready = true;
        }
        cv.notify_one();
        std::this_thread::sleep_for(std::chrono::seconds{ 1 });
    }
}

This will result in consumer printing out each unique value of data instead of skipping over any:

count: 1
count: 2
count: 3
count: 4
count: 5
count: 6

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Can std::this_thread::sleep_for() have spurious wakeups?

std::condition_variable – notify once but wait thread wakened twice

std::this_thread::sleep_for sleeps shorter than expected in VS2015

std::condition_variable without a lock

Approach of using an std::atomic compared to std::condition_variable wrt pausing & resuming an std::thread in C++

Possible race condition in std::condition_variable?

What is the maximum value I can pass to std::thread::sleep_for() and sleep_until()?

Is std::condition_variable thread-safe?

C++11 std::this_thread::sleep_until() hangs when compiled with GCC 4.8.5

Code generated - usleep vs std::this_thread::sleep_for

Why does std::condition_variable as a class member cause compile errors with std::thread?

How to use a std::condition_variable correctly?

What is s in std::this_thread::sleep_for(2s)?

Why doesn't std::this_thread::sleep_for() work in MacOS terminal?

Unpredictable behaviour of std::sleep_for on Windows 10

Does std::condition_variable::wait_until have any advantage against std::this_thread::sleep_for?

std::this_thread::sleep_for sleeps for too long

How is std::condition_variable::wait implemented?

SIGABRT on custom background task using std::thread and std::condition_variable in C++

this_thread::sleep_for sleeps early

std::this_thread::sleep_for() vs sleep() in main()

std::condition_variable::wait in c++

C++11 std::this_thread - How to cancel sleep_until ()?

How to make a thread stop excution (eg: std::this_thread::sleep_for) for an accturate interval

std::this_thread::sleep_until timing is completely off by about a factor of 2, inexplicably

std::this_thread::sleep_for() freezing the program

Where is std::this_thread for jthread?

Are std::condition_variable wait predicates thread-safe?

Why is `std::this_thread::yield()` 10x slower than `std::this_thread::sleep_for(0s)`?