I need to create an infinite loop, and in this loop there must be some function that must run in parallel. Since they access to a read-only structure, there's no risk of some race condition, so I want to run them simultaneously in order to gain some performance.
The problem is that I don't know how to achieve this result in an efficient way.
This is an example where I run four function in parallel in the loop with specific framerate (the idea from loop at specific framerate is taken from here):
#include <iostream>
#include <thread>
#include <random>
#include <condition_variable>
#include <mutex>
int getRandomIntBetween(int minValue, int maxValue) {
std::random_device rd;
std::mt19937 rng(rd());
std::uniform_int_distribution<int> uni(minValue, maxValue);
return uni(rng);
}
void fun1() {
int randomInterval = getRandomIntBetween(10, 90);
std::this_thread::sleep_for(std::chrono::milliseconds(randomInterval));
std::cout << "fun1 done in " << randomInterval << "ms" << std::endl;
}
void fun2() {
int randomInterval = getRandomIntBetween(10, 90);
std::this_thread::sleep_for(std::chrono::milliseconds(randomInterval));
std::cout << "fun2 done in " << randomInterval << "ms" << std::endl;
}
void fun3() {
int randomInterval = getRandomIntBetween(10, 200);
std::this_thread::sleep_for(std::chrono::milliseconds(randomInterval));
std::cout << "fun3 done in " << randomInterval << "ms" << std::endl;
}
void fun4() {
int randomInterval = getRandomIntBetween(3, 300);
std::this_thread::sleep_for(std::chrono::milliseconds(randomInterval));
std::cout << "fun4 done in " << randomInterval << "ms" << std::endl;
}
int main(int argc, char* argv[]) {
const int64_t frameDurationInUs = 1.0e6 / 1;
std::cout << "Parallel looping testing" << std::endl;
std::condition_variable cv;
std::mutex mut;
bool stop = false;
size_t counter{ 0 };
using delta = std::chrono::duration<int64_t, std::ratio<1, 1000000>>;
auto next = std::chrono::steady_clock::now() + delta{ frameDurationInUs };
std::unique_lock<std::mutex> lk(mut);
while (!stop) {
mut.unlock();
if (counter % 10 == 0) {
std::cout << counter << " frames..." << std::endl;
}
std::thread t1{ &fun1 };
std::thread t2{ &fun2 };
std::thread t3{ &fun3 };
std::thread t4{ &fun4 };
counter++;
t1.join();
t2.join();
t3.join();
t4.join();
mut.lock();
cv.wait_until(lk, next);
next += delta{ frameDurationInUs };
}
return 0;
}
It works but it's inefficient, because I create and delete four thread objects at every iteration.
Instead I'd like to maintain the threads always active, and then call the functions inside the loop, and using some lock mechanism (mutex, semaphore) to wait inside the loop that all functions are run completely before start the next loop iteration.
How can achieve this result?
If you do not want to rely on thread reusing, you don't have to resort to pooling:
In your very specific case you probably don't need to bother with a fully developed thread pool as you want each function to be run exactly once by the corresponding thread.
Your joins therefore become queries for the threads to be done with one particular job:
std::array<std::atomic<bool>, 4> done;
// loop:
std::fill(begin(done), end(done), false);
// ... run threads
for (std::size_t i = 0; i < 4; ++i) {
while (done[i] == false) {} // wait for thread i to finish
}
And thread i obviously then writes done[i] = true; once the function it was supposed to run is done.
You would distribute work packages in much the same way.
Related
I'm new to multithreading in C++. I just want to define a class TaskManager that allows me to handle the execution of a general task. The core logic of the task should be implemented in the task() method. Then I want to implement the start(), pause(), and resume() methods to handle the execution of task(). Is there any problem with this implementation? Is it the right way to handle this kind of problem? is there a way to abstract the core logic from the task() method?
#include <iostream>
#include <thread>
#include <chrono>
class TaskManager{
private:
std::condition_variable cv;
std::mutex mtx;
std::thread task_thread;
bool paused = true;
bool finished = false;
int counter = 0;
int MAX_COUNT = INT_MAX;
public:
~TaskManager(){
if (this->task_thread.joinable()){
this->task_thread.join();
}
}
void task(){
// Finishing condition. ==> counter < this->MAX_COUNT
while(counter < this->MAX_COUNT){
std::unique_lock<std::mutex> ul(this->mtx);
this->cv.wait(ul, [this] {return (!this->paused);});
// CORE LOGIC...
counter++;
}
std::cout << "Finished!" << std::endl;
this->finished = true;
}
void start(){
std::unique_lock<std::mutex> ul(this->mtx);
this->paused = false;
task_thread = std::thread([this]{this->task();});
cv.notify_one();
}
void pause(){
std::unique_lock<std::mutex> ul(this->mtx);
if (!this->finished) {
this->paused = true;
this->cv.notify_one();
}
}
void resume(){
std::unique_lock<std::mutex> ul(this->mtx);
if (!this->finished) {
this->paused = false;
this->cv.notify_one();
}
}
int getCounter() {
return this->counter;
}
};
int main() {
TaskManager tm;
std::cout << "counter before start(): " << tm.getCounter() << std::endl;
tm.start();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "counter after 10 ms: " << tm.getCounter() << std::endl;
tm.pause();
std::cout << "counter after pause(): " << tm.getCounter() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "counter after 10 ms: " << tm.getCounter() << std::endl;
tm.resume();
std::cout << "counter after resume(): " << tm.getCounter() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "counter after 10 ms: " << tm.getCounter() << std::endl;
return 0;
}
Output:
counter before start(): 0
counter after 10 ms: 266967
counter after pause(): 267526
counter after 10 ms: 267526
counter after resume(): 267526
counter after 10 ms: 487041
Finished!
Is there any problem with this implementation?
There's data race on counter
You probably don't want to hold the lock while executing // CORE LOGIC.... If you mean to protect counter, you should prefer another mutex for it.
finished should be under the lock too. Alternatively, it could be atomic. Note that unnecessary notifications don't hurt, so you can sip finished altogether
Is it the right way to handle this kind of problem?
Depending on why do you want to pause in the first place. For some reasons to pause there could be a better approach, like C++20 latch/semaphore/barrier.
is there a way to abstract the core logic from the task() method?
To what extent. You can change it to
void CoreLogic(std::function<void()> pause_callback) {
pause_callback();
// Core logic
}
You cannot pause a thread in an arbitrary point with C++ facilities. Maybe you can with platform facilities (like, Windows has SuspendThread), but it may not be a good idea (imagine a thread acquires malloc internal lock when paused).
In my program I need to start 2 pieces of external hardware.
This is somewhat time consuming and I therefore want to run it in separate threads.
The start-up has two parts. The second part, hardwareTask2(), must be performed ca. simultaneously on both threads.
I therefore want to use a std::barrier to synchronize before calling this method.
However, the first part of the start-up, hardwareTask1() may fail.
If it fails on either thread I want both threads to return.
How do I achieve this?
Using std::barrier::arrive_and_drop() below I have managed to at least get the other thread to finish (not wait indefinitely at the barrier).
#include <iostream>
#include <thread>
#include <barrier>
bool hardwareTask1(unsigned int id) {
srand(id);
int r = rand() % 10;
std::this_thread::sleep_for(std::chrono::seconds(r));
return true;
}
// must be called ca. simultaneously:
void hardwareTask2() {
std::this_thread::sleep_for(std::chrono::seconds(5));
}
void startHardware(unsigned int id, std::barrier<>& b) {
bool ok = hardwareTask1(id);
// Simulate that the above function failed for the first thread:
if (id == 1) {
ok = false;
}
if (!ok) {
b.arrive_and_drop();
return;
}
std::cerr << id << ": finished task1\n";
b.arrive_and_wait();
std::cerr << id << ": after barrier\n";
hardwareTask2();
}
int main()
{
std::barrier<> b(2);
std::thread t1(&startHardware, 1, std::ref(b));
std::thread t2(&startHardware, 2, std::ref(b));
t1.join();
t2.join();
std::cerr << "Both threads have finished.\n";
int k;
std::cin >> k;
}
Like #UlrichEckhardt mentioned in his comment, you can use a std::future, std::promise pair to do the synchronization. Small example:
#include <iostream>
#include <future>
#include <random>
#include <thread>
void hardwareTask1(std::promise<bool> p)
{
std::cout << "HW 1: First part\n";
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distrib(1, 10);
std::this_thread::sleep_for(std::chrono::seconds(distrib(gen)));
int res = distrib(gen);
if (res > 5) {
p.set_value(true);
std::cout << "HW 1: Second part\n";
}
else {
p.set_value(false);
std::cout << "HW 1: First part failed - abort\n";
}
}
void hardwareTask2(std::future<bool> f)
{
std::cout << "HW 2: First part\n";
if (f.get())
std::cout << "HW 2: Second part\n";
else
std::cout << "HW 2: HW 1 failed - abort\n";
}
int main()
{
std::promise<bool> p;
std::thread t2(&hardwareTask2, p.get_future());
std::thread t1(&hardwareTask1, std::move(p));
t1.join();
t2.join();
}
C++98 and Boost 1.54
I'm having trouble figuring out why using boost::this_thread::sleep_for is sleeping my entire program. The only time and place the Wait() function is called is inside this thread, and this thread's sole purpose is to read file names in a directory and trigger an upload.
But for some reason, when it reaches the boost::this_thread::sleep_for line in the Wait() function, it hangs there and sleeps all the other threads as well. I'm unsure what I am missing, so any help would be appreciated.
Code:
void Upload::ReadFileNames()
{
cout << "[DEBUG] ReadFileNames -> A " << endl;
Wait();
cout << "[DEBUG] ReadFileNames -> B " << endl;
// read filename stuff
}
void Upload::Wait()
{
typedef boost::chrono::duration<long, boost::ratio<60> > seconds;
int randomWaitTime = 0;
try{
randomWaitTime = lexical_cast<unsigned int>(getId());
randomWaitTime = randomWaitTime * 10;
}
catch ( const boost::bad_lexical_cast & e){
// cout << "[LOG] FileUpLoad : Wait : bad_lexical_cast : " << e.what() << endl ;
randomWaitTime = 0;
}
seconds testTimeToWait(randomWaitTime);
cout << "[DEBUG] Wait() -> A" << endl;
boost::this_thread::sleep_for(testTimeToWait);
cout << "[DEBUG] Wait() -> B" << endl;
cout << "RANDOM WAIT TIME = " << randomWaitTime << endl;
}
main.cpp
int main()
{
pthread_t threadA;
pthread_create(&threadA,NULL,threadAfn,NULL);
pthread_t threadB;
pthread_create(&threadB,NULL,threadBfn,NULL);
pthread_t Upload; // <--- Thread in question
pthread_create(&Upload,NULL,Uploadfn,NULL);
pthread_join(threadA,NULL);
pthread_join(threadB,NULL);
pthread_join(Upload,NULL); // <--- Thread in question
return 0;
}
Output
[DEBUG] ReadFileNames -> A
[DEBUG] Wait() -> A
// hangs here and rest of the threads are locked/slept as well?
it hangs there and sleeps all the other threads as well
No it doesn't. If it seems that way, that is because the other threads were already stuck or finished.
Look for things that block (mutex.lock, condition wait, IO operations, etc.) or check that the threads didn't exit.
Notes
Your seconds calculations is off. On my system, the following:
Live On Coliru
#include <boost/chrono.hpp>
#include <iostream>
int main() {
std::cout << boost::chrono::duration<long, boost::ratio<60> >(1)/boost::chrono::seconds(1) << std::endl;
}
Prints
60
So, what you named seconds is actually minutes. Just do this instead:
using boost::chrono::seconds;
int delay = std::strtoul(getId().c_str(), NULL, 10)*10;
sleep_for(seconds(delay));
Your random delay is only random if the getId return is. Using boost/random.hpp you can make it truly random, with good range control. E.g. to sleep between 1'000 and 3'000 ms:
int random_gen(int low, int high) { // not threadsafe
static boost::random_device rdev;
static boost::mt19937 prng(rdev);
return boost::uniform_int<>(low, high)(prng);
}
void Upload::Wait() {
int const ms_delay = random_gen(1000, 3000);
cout << "RANDOM WAIT TIME = " << ms_delay << endl;
sleep_for(milliseconds(ms_delay));
}
Note to seed using random_device as shown (so true random seed) you need to link the random library. Otherwise, you can "stoop" to a time-based seed:
static boost::mt19937 prng(std::time(NULL));
Here's a self-contained version of your code with the various suggestions applied, demonstrating that there is no deadlock/softlock:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/chrono.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <boost/random.hpp>
using boost::this_thread::sleep_for;
using boost::chrono::seconds;
using boost::chrono::milliseconds;
using boost::lexical_cast;
using std::cout;
using std::endl;
struct Upload {
std::string getId() const { return "42"; }
void Wait();
void ReadFileNames();
};
void Upload::ReadFileNames() {
cout << "[DEBUG] ReadFileNames -> A " << endl;
Wait();
cout << "[DEBUG] ReadFileNames -> B " << endl;
// read filename stuff
}
int random_gen(int low, int high) { // not threadsafe
static boost::mt19937 prng(std::time(NULL));
return boost::uniform_int<>(low, high)(prng);
}
void Upload::Wait() {
int const ms_delay = random_gen(1000, 3000);
cout << "RANDOM WAIT TIME = " << ms_delay << endl;
sleep_for(milliseconds(ms_delay));
}
void background(char const* name) {
// desync different background threads
sleep_for(milliseconds(boost::hash_value(name) % 1000));
for (int i=0; i<5; ++i) {
sleep_for(seconds(1));
std::clog << name << " " << i << std::endl;
}
}
void threadAfn() { background("thread A"); }
void threadBfn() { background("thread B"); }
void Uploadfn() {
Upload u;
u.ReadFileNames();
}
int main() {
boost::thread threadA(threadAfn);
boost::thread threadB(threadBfn);
boost::thread Upload(Uploadfn);
threadA.join();
threadB.join();
Upload.join();
}
Prints, e.g.:
[DEBUG] ReadFileNames -> A
RANDOM WAIT TIME = 1150
[DEBUG] ReadFileNames -> B
thread A 0
thread B 0
thread A 1
thread B 1
thread A 2
thread B 2
thread A 3
thread B 3
thread A 4
thread B 4
So you can create a std::future that does no work until .get() is called:
auto f_deferred = std::async( std::launch::deferred, []{ std::cout << "I ran\n"; } );
You can also write a std::future that is waitable, and can be made ready at any point by code in any thread:
std::packaged_task<void()> p( []( std::cout << "I also ran\n"; } );
auto f_waitable = p.get_future();
If you call f_deferred.wait_for(1ms), it won't bother waiting. If you call f_deferred.get(), a lambda of your choice (in this case, one that prints "I ran\n" executes.
If you call f_waitable.get(), there is no way for code managing the tasks to be aware that someone is waiting on the future. But if you call f_deferred.wait(1ms);, you simply get future_status::deferred immediately.
Is there any way I can combine these two?
A concrete use case is a thread pool returning futures when people queue tasks. If an unqueued future is .get()'d, I want to use the thread that is blocked to execute the task rather than having it idle. On the other hand, I want people with the returned futures to be able to determine if the task is finished, and even wait a bounded amount of time for the task to be finished. (in the case where you are waiting, I'm ok with your thread being idle during your wait)
Failing that, are there solutions in upcoming proposals that would solve this problem better than having my thread pool return a future with all of its limitations? I've heard that there is no future in futures and better solutions exist to the problem futures solve.
I am not sure if this is exactly what you need, but it serves the purpose of illustrating what I suggested in the comment. At the very least, I hope it gives you some ideas to implement what you need if it doesn't cover all your needs.
Disclaimer: This is very crude. A lot of things could certainly be done more elegantly and efficiently.
#include <iostream>
#include <thread>
#include <future>
#include <memory>
#include <functional>
#include <queue>
#include <random>
#include <chrono>
#include <mutex>
typedef std::packaged_task<void()> task;
typedef std::shared_ptr<task> task_ptr;
typedef std::lock_guard<std::mutex> glock;
typedef std::unique_lock<std::mutex> ulock;
typedef unsigned int uint;
typedef unsigned long ulong;
// For sync'd std::cout
std::mutex cout_mtx;
// For task scheduling
std::mutex task_mtx;
std::condition_variable task_cv;
// Prevents main() from exiting
// before the last worker exits
std::condition_variable kill_switch;
// RNG engine
std::mt19937_64 engine;
// Random sleep (in ms)
std::uniform_int_distribution<int> sleep(100, 10000);
// Task queue
std::queue<task_ptr> task_queue;
static uint tasks = 0;
static std::thread::id main_thread_id;
static uint workers = 0;
template<typename T>
class Task
{
// Not sure if this needs
// to be std::atomic.
// A simple bool might suffice.
std::atomic<bool> working;
task_ptr tp;
public:
Task(task_ptr _tp)
:
working(false),
tp(_tp)
{}
inline T get()
{
working.store(true);
(*tp)();
return tp->get_future().get();
}
inline bool is_working()
{
return working.load();
}
};
auto task_factory()
{
return std::make_shared<task>([&]
{
uint task_id(0);
{
glock lk(cout_mtx);
task_id = ++tasks;
if (std::this_thread::get_id() == main_thread_id)
{
std::cout << "Executing task " << task_id << " in main thread.\n";
}
else
{
std::cout << "Executing task " << task_id << " in worker " << std::this_thread::get_id() << ".\n";
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(sleep(engine)));
{
glock lk(cout_mtx);
std::cout << "\tTask " << task_id << " completed.\n";
}
});
}
auto func_factory()
{
return [&]
{
while(true)
{
ulock lk(task_mtx);
task_cv.wait(lk, [&]{ return !task_queue.empty(); });
Task<void> task(task_queue.front());
task_queue.pop();
// Check if the task has been assigned
if (!task.is_working())
{
// Sleep for a while and check again.
// If it is still not assigned after 1 s,
// start working on it.
// You can also place these checks
// directly in Task::get()
{
glock lk(cout_mtx);
std::cout << "\tTask not started, waiting 1 s...\n";
}
lk.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
lk.lock();
if (!task.is_working())
{
{
glock lk(cout_mtx);
std::cout << "\tTask not started after 1 s, commencing work...\n";
}
lk.unlock();
task.get();
lk.lock();
}
if (task_queue.empty())
{
break;
}
}
}
};
}
int main()
{
engine.seed(std::chrono::high_resolution_clock::now().time_since_epoch().count());
std::cout << "Main thread: " << std::this_thread::get_id() << "\n";
main_thread_id = std::this_thread::get_id();
for (int i = 0; i < 50; ++i)
{
task_queue.push(task_factory());
}
std::cout << "Tasks enqueued: " << task_queue.size() << "\n";
// Spawn 5 workers
for (int i = 0; i < 5; ++i)
{
std::thread([&]
{
{
ulock lk(task_mtx);
++workers;
task_cv.wait(lk);
{
glock lk(cout_mtx);
std::cout << "\tWorker started\n";
}
}
auto fn(func_factory());
fn();
ulock lk(task_mtx);
--workers;
if (workers == 0)
{
kill_switch.notify_all();
}
}).detach();
}
// Notify all workers to start processing the queue
task_cv.notify_all();
// This is the important bit:
// Tasks can be executed by the main thread
// as well as by the workers.
// In fact, any thread can grab a task from the queue,
// check if it is running and start working
// on it if it is not.
auto fn(func_factory());
fn();
ulock lk(task_mtx);
if (workers > 0)
{
kill_switch.wait(lk);
}
return 0;
}
This is my CMakeLists.txt
cmake_minimum_required(VERSION 3.2)
project(tp_wait)
set(CMAKE_CXX_COMPILER "clang++")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE)
find_package(Threads REQUIRED)
add_executable(${PROJECT_NAME} "main.cpp")
target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})
I was trying to write code for Producer-Consumer problem. Below code works fine most of the time but stuck sometimes because of "Lost Wake-up" (i guess). I tried thread sleep() but it didn't work. What modification is needed to handle this case in my code? Is semaphore can be helpful here ? If yes, how will i implement them here ?
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
using namespace std;
int product = 0;
boost::mutex mutex;
boost::condition_variable cv;
boost::condition_variable pv;
bool done = false;
void consumer(){
while(done==false){
//cout << "start c" << endl
boost::mutex::scoped_lock lock(mutex);
cv.wait(lock);
//cout << "wakeup c" << endl;
if (done==false)
{
cout << product << endl;
//cout << "notify c" << endl;
pv.notify_one();
}
//cout << "end c" << endl;
}
}
void producer(){
for(int i=0;i<10;i++){
//cout << "start p" << endl;
boost::mutex::scoped_lock lock(mutex);
boost::this_thread::sleep(boost::posix_time::microseconds(50000));
++product;
//cout << "notify p" << endl;
cv.notify_one();
pv.wait(lock);
//cout << "wakeup p" << endl;
}
//cout << "end p" << endl;
cv.notify_one();
done = true;
}
int main()
{
int t = 1000;
while(t--){
/*
This is not perfect, and is prone to a subtle issue called the lost wakeup (for example, producer calls notify()
on the condition, but client hasn't really called wait() yet, then both will wait() indefinitely.)
*/
boost::thread consumerThread(&consumer);
boost::thread producerThread(&producer);
producerThread.join();
consumerThread.join();
done =false;
//cout << "process end" << endl;
}
cout << "done" << endl;
getchar();
return 0;
}
Yes, you want a way to know (in the consumer) that you "missed" a signal. A semaphore can help. There's more than one way to skin a cat, so here's my simple take on it (using just c++11 standard library features):
class semaphore
{
private:
std::mutex mtx;
std::condition_variable cv;
int count;
public:
semaphore(int count_ = 0) : count(count_) { }
void notify()
{
std::unique_lock<std::mutex> lck(mtx);
++count;
cv.notify_one();
}
void wait() { return wait([]{}); } // no-op action
template <typename F>
auto wait(F&& func = []{}) -> decltype(std::declval<F>()())
{
std::unique_lock<std::mutex> lck(mtx);
while(count == 0){
cv.wait(lck);
}
count--;
return func();
}
};
For convenience, I added a convenience wait() overload that takes a function to be executed under the lock. This makes it possible for the consumer to operate the 'semaphore' without ever manually operating the lock (and still get the value of product without data-races):
semaphore sem;
void consumer() {
do {
bool stop = false;
int received_product = sem.wait([&stop] { stop = done; return product; });
if (stop)
break;
std::cout << received_product << std::endl;
std::unique_lock<std::mutex> lock(processed_mutex);
processed_signal.notify_one();
} while(true);
}
A fully working demo: Live on Coliru:
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <cassert>
class semaphore
{
private:
std::mutex mtx;
std::condition_variable cv;
int count;
public:
semaphore(int count_ = 0) : count(count_) { }
void notify()
{
std::unique_lock<std::mutex> lck(mtx);
++count;
cv.notify_one();
}
void wait() { return wait([]{}); } // no-op action
template <typename F>
auto wait(F&& func = []{}) -> decltype(std::declval<F>()())
{
std::unique_lock<std::mutex> lck(mtx);
while(count == 0){
cv.wait(lck);
}
count--;
return func();
}
};
semaphore sem;
int product = 0;
std::mutex processed_mutex;
std::condition_variable processed_signal;
bool done = false;
void consumer(int check) {
do {
bool stop = false;
int received_product = sem.wait([&stop] { stop = done; return product; });
if (stop)
break;
std::cout << received_product << std::endl;
assert(++check == received_product);
std::unique_lock<std::mutex> lock(processed_mutex);
processed_signal.notify_one();
} while(true);
}
void producer() {
std::unique_lock<std::mutex> lock(processed_mutex);
for(int i = 0; i < 10; ++i) {
++product;
sem.notify();
processed_signal.wait(lock);
}
done = true;
sem.notify();
}
int main() {
int t = 1000;
while(t--) {
std::thread consumerThread(&consumer, product);
std::thread producerThread(&producer);
producerThread.join();
consumerThread.join();
done = false;
std::cout << "process end" << std::endl;
}
std::cout << "done" << std::endl;
}
You seems to ignore that the variable done is also a shared state, to the same extend as product. Which can lead to several races conditions. In your case, I see at least one scenario where consumerThread make no progress:
The loop execute has intended
consumer executes, and is waiting at cv.wait(lock);
producer has finished the for loop, and notify consumer and is preempted
consumer wakes up, read "done==false", output product, read done == false again, wait on the condition
producer set done to true and exit
consumer is stuck forever
To avoid these kind of issues you should be holding a lock when reading or writing done. Btw your implementation is quite sequential, ie the producer and the consumer can only process a single piece of data at the time...