Producer Consumer problem using mutexes in cpp - c++

I have a single producer and 2 consumers threads trying to acess a shared buffer. Mutex locks are used between consumer and producer. Consumers are supposed to run paralelly. If buffer is empty, consumer sleeps and producer has to wake them. If buffer is full, producer does not do anything. Below is the code snippets I am working on:
Producer thread:
void *writer(void*)
{
// Initialising the seed
srand(time(NULL));
while(1)
{
pthread_mutex_lock(&rallow);
if (Q.size() < MAX && item < MAX)
{
// Getting the random number
int num = rand() % 10 + 1;
// Pushing the number into queue
Q.push(num);
item++;
cout << "Produced: " << num << " item: "<<item<<endl;
pthread_cond_broadcast(&dataNotProduced);
}
else if (item == MAX) {
pthread_mutex_unlock(&rallow);
continue;
}
pthread_mutex_unlock(&rallow);
}
}
COnsumer 1:
void *reader1(void*)
{
while(1)
{
pthread_mutex_lock(&mread);
rc++;
if(rc==1)
pthread_mutex_lock(&rallow);
pthread_mutex_unlock(&mread);
if (Q.size() > 0) {
// Get the data from the front of queue
int data = Q.front();
// Pop the consumed data from queue
Q.pop();
item--;
cout << "B thread consumed: " << data <<endl;
pthread_cond_signal(&dataNotConsumed);
}
else
{
cout << "B is in wait.." << endl;
pthread_cond_wait(&dataNotProduced, &rallow);
cout<<"B woke up"<<endl;
}
pthread_mutex_lock(&mread);
rc--;
if(rc==0)
pthread_mutex_unlock(&rallow);
pthread_mutex_unlock(&mread);
sleep(1);
}
}
Consumer 2:
void *reader2(void*)
{
while(1)
{
pthread_mutex_lock(&mread);
rc++;
if(rc==1)
pthread_mutex_lock(&rallow);
pthread_mutex_unlock(&mread);
if (Q.size() > 0) {
// Get the data from the front of queue
int data = Q.front();
// Pop the consumed data from queue
Q.pop();
item--;
cout << "C thread consumed: " << data <<endl;
pthread_cond_signal(&dataNotConsumed);
}
else
{
cout << "C is in wait.." << endl;
pthread_cond_wait(&dataNotProduced, &rallow);
cout<<"C woke up"<<endl;
}
pthread_mutex_lock(&mread);
rc--;
if(rc==0)
pthread_mutex_unlock(&rallow);
pthread_mutex_unlock(&mread);
sleep(1);
}
}
The output looks something like this:
C is in wait..
B is in wait..
Produced: 8 item: 1
Produced: 4 item: 2
Produced: 2 item: 3
Produced: 4 item: 4
Produced: 2 item: 5
Produced: 8 item: 6
Produced: 5 item: 7
Produced: 2 item: 8
Produced: 10 item: 9
Produced: 3 item: 10
>> Producer is in wait..
B woke up
B thread consumed: 8
B thread consumed: 4
B thread consumed: 2
B thread consumed: 4
B thread consumed: 2
B thread consumed: 8
B thread consumed: 5
B thread consumed: 2
B thread consumed: 10
B thread consumed: 3
B is in wait..
C woke up
C is in wait..
Producer woke up
My doubt is why threads B and C not showing parallel execution. And why does producer fill values into the buffer 10 at a time, rather than giving few, and then consumers consuming it, then again producing few. ANy leads would be highly appreciated.

else if (item == MAX) {
pthread_mutex_unlock(&rallow);
cout << ">> Producer is in wait.." << endl;
pthread_cond_wait(&dataNotConsumed, &rallow);
You unlock the mutex and then wait. You can't do that. That creates a window during which the thing you are waiting for can occur before you wait. You must call pthread_cond_wait while holding the mutex to ensure that the thing you are waiting for doesn't happen after you've decided to wait but before you've started waiting.
You have another huge bug in your consumer. One thread can lock rallow and then another thread can try to unlock it. That's not allowed -- the thread that acquires the mutex must be the one to release it. You don't need two mutexes -- just use one that protects all state.

First of all, there is no guarantee that all threads will run concurrently all the time. If they run on a single core, the operating system will give time slices of tens of milliseconds to each thread. And if they are running on different cores, then there is a latency between one thread calling pthread_cond_broadcast() and another thread waking up from a pthread_cond_wait(). This easily explains the writer thread being able to push 10 items to the queue before another thread wakes up.
The next issue is, why does B consume all the items, and C gets nothing? The problem is because of this:
pthread_mutex_lock(&mread);
rc++;
if(rc == 1)
pthread_mutex_lock(&rallow);
pthread_mutex_unlock(&mread);
Consider threads B and C each executing this block right after each other. Both will be able to lock mread, both will increment rc, but only one will have locked rallow. What happens next is undefined, because they both try to access the queue, even though one of them will not be holding the lock.
There should be no need to have two mutexes. Both consumer threads should just lock rallow unconditionally, check if there is something in the queue, and if not call pthread_cond_wait().
Since you are using C++, you should really use C++11's thread support instead of using the C pthread functions. Your code should then look like:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex rallow;
std::condition_variable dataProduced;
std::condition_variable dataConsumed;
void writer() {
while(true) {
// Generate the random number
int num = rand() % 10 + 1;
std::cout << "Produced: " << num << "\n";
// Push it to the queue
{
std::lock_guard<std::mutex> lock(rallow);
dataConsumed.wait(rallow, [](){return Q.size() < MAX;});
Q.push(num);
}
}
}
void reader(int id) {
while(true) {
int data;
// Pop an item from the queue
{
std::lock_guard<std::mutex> lock(rallow);
dataProduced.wait(rallow, [](){return Q.size() > 0;});
data = Q.front();
Q.pop();
}
// Process the data
std::cout << "Consumer thread " << id << " consumed: " << data << "\n";
}
}
You could even create a thread-safe queue class that handles the mutexes and condition variables itself, so the producer and consumer code would reduce to:
void writer() {
while(true) {
int num = rand() % 10 + 1;
std::cout << "Produced: " << num << "\n";
Q.push(num);
}
}
void reader(int id) {
while(true) {
int data = Q.pop();
std::cout << "Consumer thread " << id << " consumed: " << data << "\n";
}
}

Related

Same thread keeps getting rescheduled after yielding

I am trying to create a very threaded simple producer consumer toy implementation, but I'm running into a strange issue where the same consumer thread keeps getting rescheduled over and over again even though I am yielding control.
Here is an abridged version of my code. The Main method is simple , I start one producer and two consumer threads. I join to the producer thread and detach the two consumer threads. A getchar at the end of the main method keeps the program from exiting.
std::vector<int> UnprocessedValues;
std::vector<int> ProcessedValues;
std::mutex unprocessed_mutex;
void AddUnprocessedValue()
{
for (int i = 0; i <= 1000; ++i)
{
{
std::lock_guard<std::mutex> guard(unprocessed_mutex);
UnprocessedValues.push_back(i);
std::cout << "Thread id : " << std::this_thread::get_id() << " ";
printf("Unprocessed value added %i \n", UnprocessedValues.back());
}
}
}
void ProcessCurrentValue()
{
while (true)
{
unprocessed_mutex.lock();
if (UnprocessedValues.empty())
{
std::cout << "Thread id : " << std::this_thread::get_id() << " ";
printf("is waiting for values \n");
unprocessed_mutex.unlock();
std::this_thread::yield();
}
else
{
// Process value
unprocessed_mutex.unlock();
}
}
}
I expect that when there are no values present for consumers, they will both yield and end up giving the producer a chance to produce more.
In practice I see a single consumer getting stuck on waiting for values. Eventually the program rights itself, but something is obviously wrong.
If I was seeing the two consumers print that they are waiting in alternate, I would think that somehow the producer is getting shafted by the two consumers taking turns, but the actual result is that the same thread keeps getting rescheduled even though it just yielded.
Finally, when I change the if case from
if (UnprocessedValues.empty())
{
std::cout << "Thread id : " << std::this_thread::get_id() << " ";
printf("is waiting for values \n");
unprocessed_mutex.unlock();
std::this_thread::yield();
}
to
if (UnprocessedValues.empty())
{
unprocessed_mutex.unlock();
std::this_thread::yield();
std::cout << "Thread id : " << std::this_thread::get_id() << " ";
printf("is waiting for values \n");
}
I never see a busy wait. I realize that I could use a condition variable to fix this problem and I have already seen that using a small sleep instead of a yield works. I am just trying to understand why the yield would not work.

C++ threads why the cout statements are not getting printed even after fflush

I have a sample code:
#include <iostream> // std::cout
#include <thread> // std::thread
void pause_thread(int n)
{
if(n != 4)
{
std::this_thread::sleep_for(std::chrono::seconds(100));
std::cout << "pause of " << 100 << " seconds ended\n";
}
std::cout << "Thread number " << n << " ended\n";
}
int main()
{
std::thread threads[6]; // default-constructed threads
std::setvbuf(stdout, NULL, _IONBF, 0);
std::cout << "Spawning 5 threads...\n";
for(int i = 0; i < 5; ++i)
{
//If the object is currently not joinable, it acquires the thread of execution represented by rhs (if any).
//If it is joinable, terminate() is called. If it is joinable, terminate() is called.
//rhs no longer represents any thread of execution
threads[i] = std::move(std::thread(pause_thread, i)); // move-assign threads
}
std::thread& i = threads[4];
threads[5] = std::move(threads[4]);
std::cout << "Done spawning threads. Now waiting for them to join:\n";
for(int i = 0; i < 6; ++i)
{
if(threads[i].joinable())
{
std::cout << "Thread " << i << " " << threads[i].get_id() << " ID joinable" << std::endl << std::flush;
threads[i].join();
}
else
{
std::cout << "Thread " << i << " not joinable" << std::endl << std::flush;
}
}
std::cout << "All threads joined!\n";
return 0;
}
Below is the output I received:
Spawning 5 threads...
Done spawning threads. Now waiting for them to join:
Thread 0 22476 ID joinable
Thread number 4 ended
.... no output for 100 seconds ..
pause of 100 seconds ended
Thread number 0 ended
pause of 100 seconds ended
Thread 1 28676 ID joinable
pause of 100 seconds ended
Thread number 2 ended
Thread number 3 ended
pause of 100 seconds ended
Thread number 1 ended
Thread 2 2336 ID joinable
Thread 3 42236 ID joinable
Thread 4 not joinable
Thread 5 35940 ID joinable
All threads joined!
How the "Thread n xxxx ID joinable" statements are getting printed after "Thread number n ended"? I have even tried using set std::output as non buffered but the output was same?
"Joinable" does not imply that the thread is still executing.
You first join thread #0. This will take ~100 seconds.
During that time, thread #4 finishes since it doesn't sleep, and the other threads are sleeping.
If the threads happen to be scheduled differently, any of the "sleep threads" could be printing that they've ended here.
Once the wait for thread #0 is over, you start joining the other threads.
Some of these have finished executing before you join them and some haven't.
In this particular instance, none of them finished before the wait for thread #0 was over, but there is no guarantee of that happening.
And note that a line like
std::cout << "Thread number " << n << " ended\n";
is not atomic and characters from different threads can be interleaved.
Because joinable does not mean what you think : https://en.cppreference.com/w/cpp/thread/thread/joinable
So any thread that is started is "joinable" it does not need to have finished running.

Condition Variable with a while loop in thread in C++

I'm trying to figure out how to use std::condition_variable in C++ implementing a "strange" producer and consumer program in which I had set a limit to the count variable.
The main thread ("producer") increments the count and must wait for this to return to zero to issue a new increment.
The other threads enters in a loop where they have to decrease the counter and issue the notification.
I am blocked because it is not clear to me how to conclude the program by orderly exiting the while loop inside the function of all threads.
Could someone give me some guidance on how to implement it, please?
Code
#include <iostream>
#include <thread>
#include <condition_variable>
#include <vector>
int main() {
int n_core = std::thread::hardware_concurrency();
std::vector<std::thread> workers;
int max = 100;
int count = 0;
std::condition_variable cv;
std::mutex mutex;
int timecalled = 0;
for (int i = 0; i < n_core; i++) {
workers.emplace_back(std::thread{[&max, &count, &mutex, &cv]() {
while (true) {
std::unique_lock<std::mutex> lk{mutex};
std::cout << std::this_thread::get_id() << " cv" << std::endl;
cv.wait(lk, [&count]() { return count == 1; });
std::cout << std::this_thread::get_id() << " - " << count << std::endl;
count--;
std::cout << std::this_thread::get_id() << " notify dec" << std::endl;
cv.notify_all();
}
}});
}
while (max > 0) {
std::unique_lock<std::mutex> lk{mutex};
std::cout << std::this_thread::get_id() << " cv" << std::endl;
cv.wait(lk, [&count]() { return count == 0; });
std::cout << std::this_thread::get_id() << " created token" << std::endl;
count++;
max--;
timecalled++;
std::cout << std::this_thread::get_id() << " notify inc" << std::endl;
cv.notify_all();
}
for (auto &w : workers) {
w.join();
}
std::cout << timecalled << std::endl; // must be equal to max
std::cout << count << std::endl; // must be zero
}
Problem
The program doesn't end because it is stuck on some final join.
Expected Result
The expected result must be:
100
0
Edits Made
EDIT 1 : I replaced max > 0 in the while with a true. Now the loops are unbounded, but using the solution of #prog-fh seems to work.
EDIT 2 : I added a variable to check the result in the end.
EDIT 3: I changed while(true) to while(max >0). Could this be a problem in concurrency because we are reading it without a lock?
The threads are waiting for something new in the call cv.wait().
But the only change that can be observed with the provided lambda-closure is the value of count.
The value of max must be checked too in order to have a chance to leave this cv.wait() call.
A minimal change in your code could be
cv.wait(lk, [&max, &count]() { return count == 1 || max<=0; });
if(max<=0) break;
assuming that changes to max always occur under the control of the mutex.
An edit to clarify around the accesses to max.
If the loop run by the threads is now while(true), then the max variable is only read in its body which is synchronised by mutex (thanks to lk).
The loop run by the main program is while (max > 0): max is read without synchronisation here but the only thread that can change this variable is the main program itself, so it's pure serial code from this perspective.
The whole body of this loop is synchronised by mutex (thanks to lk) so it is safe to change the value of max here since the read operations in the threads are synchronised in the same way.
You're having race conditions: in your code max may be read by multiple threads, whilst it is being modified in main, which is a race condition according to C++ standard.
The predicates you are using in wait seems to be incorrect (you're using ==).

Having set amount of thread to work as consumers

I have created a producer / consumer code as following
class CTest{
public:
void producer( int i ){
unique_lock<mutex> l(m);
q.push(i);
if( q.size() )
cnd.notify_all();
}
void consumer(int i ){
unique_lock<mutex> l(m);
while( q.empty() ){
cnd.wait(l );
}
if( q.empty())
return;
cout << "IM AWAKE :" << i << endl;
int tmp = q.front();
q.pop();
l.unlock();
cout << "Producer got " << tmp << endl;
}
void ConsumerInit( int threads ){
for( int i = 0; i < threads; i++ ){
thrs.push_back(thread(&CTest::consumer, this ,i));
}
}
void waitForTHreads(){
for( auto &a : thrs )
a.join();
}
void printQueue(){
while( ! q.empty()){
int tmp = q.front();
q.pop();
cout << "Queue got " << tmp << endl;
}
}
private:
queue<int> q;
vector<thread> thrs;
mutex m;
condition_variable cnd;
};
and main
int main(){
int x;
CTest t;
int counter = 0;
while( cin >> x ){
if( x == 0 ){
cout << "yay" << endl;;
break;
}
if( x == 1)
t.producer(counter++);
if( x == 2 )
t.ConsumerInit(5);
}
t.waitForTHreads();
t.printQueue();
return 0;
}
What this code does it , when user inputs "1" it will add number to the queue ,when user inputs "2" , 5 threads are spawned to retrieve data from queue and print it. However my problem is as followng , when i input
6 numbers , only 5 of them are printed due to fact that only 5 threads are spawned , what i want to do is thread to retrieve a data from queue , print int, and then again waiting if it can print another data. This way all N > 5 numbers would pri printed with just 5 threads.
My question is , what is standard way how to achieve this? I read few documens but didnt fint/cannot think of good solution. How are problems like this solved?
when i try to create simple thread pool :
void consumer(int i ){
while(true){
{
unique_lock<mutex> l(m);
while( q.empty() ){
cnd.wait(l );
}
if( q.empty())
return;
cout << "IM AWAKE :" << i << endl;
int tmp = q.front();
q.pop();
cout << "Producer " << i << " got " << tmp << endl;
} //consumer(i);
}
}
and input N number all numbers are processed by one thread.
Thanks for help!
The current version of consumer can only read one value before exiting. In order to read more, it must loop, and this leads to your second version of consumer which has two problems:
Consumption here is so quick that the first thread into the queue can consume the whole queue within its timeslice (or however CPU is being allocated). Insert a yield or a sleep to force the OS to switch tasks.
The mutex is not unlocked so no other threads are able to get in.
Fortunately you aren't creating the threads until you need them and they terminate after the queue is empty so the whole deal with conditional_variable can go out the window.
void consumer(int i)
{
unique_lock<mutex> l(m);
while (!q.empty())
{
int tmp = q.front();
q.pop();
cout << i << " got " << tmp << endl;
// note: In the real world, locking around a cout is gross. cout is slow,
// so you want the unlock up one line. But...! This allows multiple threads
// to write to the consle at the same time and that makes your output
// look like it was tossed into a blender, so we'll take the performance hit
l.unlock(); // let other threads have a turn
this_thread::yield();
l.lock(); // lock again so the queue can be safely inspected
}
}
If you need to go with the threadpool approach, things get a little messier and the condition variable makes a return.
void consumer(int i)
{
while (true)
{
unique_lock<mutex> l(m);
if (q.empty())
{
cnd.wait(l);
}
if (!q.empty()) // OK. We got out of the conditional wait, but have
// other threads sucked the queue dry? Better check.
{
int tmp = q.front();
q.pop();
cout << i << " got " << tmp << endl;
}
l.unlock();
this_thread::yield();
}
}
An atomic<bool> terminated may be helpful to allow an orderly shutdown while (true) does not allow for.
In general, without going into code details, a threadpool is created and the threads are put in a wait state (waiting on one or more events / signals, or in your case condition_variable cnd;) - I'm used to work with events, so I'll use that in the following text, but a condition_variable should work in a similar way.
When a task is added to the queue, a task-event is set/fired and one ore more threads wake up (depending on the event (single / multi)).
When a thread wakes up, it checks (with a lock) if there is a task available, if available, executes the task and when finished checks again (!) if there are more tasks waiting. (because when you add 8 tasks in one go, 5 threads become active, so they need to check if there are more tasks after finishing their first one.
If there are no jobs left, the thread goes back in the wait state (waiting for a next job, or a quit event).
When quitting the application, another, say quit-event, is set for all threads (you can't just wait for the threads to finish, because the threads themselves are waiting on an event to do some work) -- or you could fire the same event, and first set a volatile variable, which the threads should then first check on any event to see if they need to quit, or do another job. Then you can wait for the threads to 'come home'.
A lock should be held as short as possible.
As for your code:
void producer( int i ){
unique_lock<mutex> l(m);
q.push(i);
if( q.size() )
cnd.notify_all();
}
Here the lock is held longer than needed (and perhaps too long). You also just pushed a value, so q will not be empty (no need to check). Since you only add one item (task), only one thread should be woken up (so notify_one() should be fine here).
So you should: lock, push, unlock, notify - instead of unlock, you can place the lock and push inside brackets, which will trigger an unlock in the unique_lock<> destructor.
void consumer(int i ){
unique_lock<mutex> l(m);
while( q.empty() ){
cnd.wait(l );
}
if( q.empty())
return;
cout << "IM AWAKE :" << i << endl;
int tmp = q.front();
q.pop();
l.unlock();
cout << "Producer got " << tmp << endl;
}
Here you should lock, check queue, pop if there is a task, unlock, if no task, put the thread in a wait state again, else do work with the popped value (after unlocking), and then check again if there is more work to do. Normally it is not a good idea to call cout while the data is locked.. but for a small test you could get away with it, especially because cout needs to be synchronized too (but it would be cleaner to synchronize cout on its own, separate from your data lock).
void printQueue(){
while( ! q.empty()){
int tmp = q.front();
q.pop();
cout << "Queue got " << tmp << endl;
}
}
Make sure your data is locked here too! (although it's only called from main after the threads have finished, the function is in your class, and the data should be locked).

How to synchronize instances of a function running on different threads (in c++11)?

Let's say there's a number of threads that consist of a loop running instances of the same function, but the start of every iteration needs to be synchronized (so the threads that finish first have to wait for the last one to begin a new iteration). How can this be done in c++11?
...
The rest of the post is just what I've tried and how it fails.
I'm using a counter, "sync", initially set to 3 (the number of threads). Every thread, at the end of the function will subtract 1 from this counter and start waiting. When the counter reaches 0, that means that the 3 of them have finished one round, so the main thread will reset the counter to 3 and notify the threads to wake them up.
This works most of the time but sometimes one or two of the threads fail to wake up.
So these are the global variables:
mutex syncMutex;
condition_variable syncCV;
int sync;
This is at the end of the function that runs in a loop in the threads:
unique_lock<mutex> lk(syncMutex);
cout << "Thread num: " << mFieldNum << " got sync value: " << sync;
sync --;
syncCV.notify_all();
cout << " and goes to sleep..." << endl;
syncCV.wait(lk, []{return sync == numFields;});
cout << "Thread num: " << mFieldNum << " woke up" << endl;
}
And this runs in a loop in the main thread:
unique_lock<mutex> lk(syncMutex);
syncCV.wait(lk, []{return sync == 0;});
sync = 3;
lk.unlock();
cout << "Notifying all threads!" << endl;
syncCV.notify_all();
This is the output it produces when it fails (thread #3 doesn't wake up):
Thread num: 1 got sync value: 3 and goes to sleep...
Thread num: 2 got sync value: 2 and goes to sleep...
Thread num: 3 got sync value: 1 and goes to sleep...
Notifying all threads!
Thread num: 1 woke up
Thread num: 2 woke up
Thread num: 3 woke up
Thread num: 2 got sync value: 3 and goes to sleep...
Thread num: 1 got sync value: 2 and goes to sleep...
Thread num: 3 got sync value: 1 and goes to sleep...
Notifying all threads!
Thread num: 2 woke up
Thread num: 1 woke up
Thread num: 2 got sync value: 3 and goes to sleep...
Thread num: 1 got sync value: 2 and goes to sleep...
Does anyone have a clue? Thank you for reading.
There are a number of issues with your thread synchronization. Tony has mentioned one in his comment. You also have a potential race condition in your main loop code where you call lk.unlock() before calling syncCV.notify_all(). (This could permit a thread to miss the notify_all signal.)
I would adjust your code in two ways. First, to address the use of "sync == numFields" as your condition, which, as Tony noted, can fail to be true after another thread has executed sync--, it makes sense to use as your condition that each thread run only once per main-thread loop. In my example code, this is achieved by introducing the "done[numFields]" variables. Second, it makes sense to introduce two condition variables -- one to signal the worker threads that a new main-loop iteration has started, and a second to signal the main thread that the worker threads are done. (Notice that the two condition variables use the same mutex.)
Here is a complete program, modelled on your sample code, that incorporates these two approaches:
#include <iostream>
using std::cout;
using std::endl;
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
std::mutex syncMutex;
std::condition_variable readyCV;
std::condition_variable doneCV;
int sync;
bool exitFlag;
const int numFields = 5;
bool done[numFields];
const int nloops = 10;
void thread_func(int i) {
int mFieldNum = i;
while (true) {
std::unique_lock<std::mutex> lk(syncMutex);
readyCV.wait(lk, [mFieldNum]{return exitFlag || !done[mFieldNum-1];});
if (exitFlag) break;
cout << "Thread num: " << mFieldNum << " woke up, got sync value: " << sync;
if (--sync == 0) doneCV.notify_all();
done[mFieldNum-1] = true;
readyCV.notify_all();
cout << " and goes to sleep..." << endl;
}
}
int main (int argc, char* argv[]) {
exitFlag = false;
sync = 0;
std::vector<std::thread> threads;
for (int i = 0; i < numFields; i++) {
done[i] = true;
threads.emplace_back (thread_func, i+1);
}
for (int i = 0; i <= nloops; i++) {
std::unique_lock<std::mutex> lk(syncMutex);
doneCV.wait(lk, []{return sync == 0;});
cout << "main loop (lk held), i = " << i << endl;
sync = numFields;
if (i == nloops) exitFlag = true;
else for (auto &b : done) b = false;
cout << "Notifying all threads!" << endl;
readyCV.notify_all();
}
for (auto& t : threads) t.join();
}
(I've also added an exitFlag and std::thread::join()'s so the program can clean up and terminate nicely.)
This is very similar to a classic producer-consumer implementation (one producer, numFields consumers), with the added constraint that each consumer thread will run only once per producer thread loop.
You can also achieve essentially the same program logic more simply if you are willing to forgo reusing the worker threads. (In your sample code and my above example, they are acting as a sort of specialized thread pool.) In my next example, new threads are created for each iteration of the main loop. This makes the thread synchronization simpler and eliminates the condition variables.
#include <iostream>
using std::cout;
using std::endl;
#include <atomic>
#include <mutex>
#include <thread>
#include <vector>
std::mutex coutMutex;
std::atomic<int> sync;
const int numFields = 5;
bool done[numFields];
const int nloops = 10;
void thread_func(int i) {
int mFieldNum = i;
int mySync = sync--;
{
std::lock_guard<std::mutex> lk(coutMutex);
cout << "Thread num: " << mFieldNum << " woke up, got sync value: " << mySync << endl;
}
}
int main (int argc, char* argv[]) {
for (int i = 0; i < nloops; i++) {
cout << "main loop, i = " << i << endl;
std::vector<std::thread> threads;
sync = numFields;
for (int i = 0; i < numFields; i++) threads.emplace_back (thread_func, i+1);
for (auto& t : threads) t.join();
}
}
(coutMutex is a nicety so that the console output doesn't get garbled, but it is not necessary for the core synchronization logic.)
If in your real-world use case you don't need the thread_func to stay alive from iteration to iteration (for example, to preserve some state), and if each call to thread_func does enough work that the cost of creating a new thread to run it doesn't really matter in comparison, then creating new threads for each main-loop iteration (instead of reusing threads) is straightforward, sensible, and simpler.
Happy Multi-Threaded Hacking!
K. Frank