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();
}
CodePudding user response:
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, it's effectively a deadlock. When the sleep statement returns, the producer
thread is just now resuming with a full quantum. Hence, it likely has enough time left in its quantum to invoke ready=true
followed by releasing of the lock, followed by it grabbing it again and going back to sleep.
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
statement.
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