Why is this c++ multithreading mutex code exhibiting occasional failures? - c++

I am using this foo.cpp code below on a linux Debian system:
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <thread>
std::mutex mtx;
std::condition_variable cvar;
long next = 0;
void doit(long index){
std::unique_lock<std::mutex> lock(mtx);
cvar.wait(lock, [=]{return index == next;});
std::cout<< index << std::endl;
++next;
mtx.unlock();
cvar.notify_all();
return;
}
int main()
{
long n=50;
for (long i=0; i < n; ++i)
std::thread (doit,i).detach();
while(next != n)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return(0);
}
I compile it with:
g++ -std=c++14 -pthread -o foo foo.cpp
It is designed to fire off 50 threads, detached, which are controlled by a mutex and condition_variable in the function doit, so they execute the mutex block sequentially.
It works most of the time, writing the numbers 00 to 49 to the screen, and then terminating.
However, it has two occasional failure modes:
Failure mode 1: After going up to some arbitrary number < 50, it aborts with the error:
foo: ../nptl/pthread_mutex_lock.c:80: __pthread_mutex_lock: Assertion `mutex->__data.__owner == 0' failed.
Failure mode 2: After going up to some arbitrary number < 50, it hangs, and has to be killed with ctrl-C to get back to the terminal prompt.
I would appreciate any suggestions as to the causes of this behavior, and how I can fix it.
=========================================================================
Edit: Ok, so here is a working revised version. I fixed the two bugs, and also changed the lock name from "lock" to "lk" to reduce confusion. Thanks for the help.
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
std::mutex mtx;
std::condition_variable cvar;
long next = 0;
void doit(long index){
std::unique_lock<std::mutex> lk(mtx);
cvar.wait(lk, [=]{return index == next;});
std::cout<< index << std::endl;
++next;
lk.unlock();
cvar.notify_all();
return;
}
int main()
{
long n=50;
for (long i=0; i < n; ++i)
std::thread (doit,i).detach();
{
std::unique_lock<std::mutex> lk(mtx);
cvar.wait(lk, [=]{return n == next;});
}
return(0);
}

while(next != n) attempts to access variable next that can be modified by working threads without any sync creating a race condition. It should be covered by the same mutex:
{
std::unique_lock<std::mutex> lock(mtx);
cvar.wait(lock, [=]{return n == next;});
}
Detaching threads is not a good idea. You should store them somewhere and then join before returning from main.
Update: You are trying to call unlock on mutex itself instead of calling it on lock object. By constructing lock object you are delegating responsibility for unlocking mutex to lock object. It should be
lock.unlock();
cvar.notify_all();

I don't recommand detaching the threads, since you cannot join them after that.
If you really want to do it, then use condition variable to synchronize data for the while next.
void doit(long index){
std::unique_lock<std::mutex> lock(mtx);
cvar.wait(lock, [=]{return index == next;});
std::cout<< index << std::endl;
++next;
cvar.notify_all();
return;
}
int main()
{
long n=50;
for (long i=0; i < n; ++i)
std::thread (doit,i).detach();
//here you wait for the last thread to finish
{
std::unique_lock<std::mutex> lock(mtx);
cvar.wait(lock, [=]{return n == next;});
}
return(0);
}
If you can have your thread being joinable you can write simpler code.
std::mutex mtx;
std::condition_variable cvar;
long next = 0;
void doit(long index){
std::unique_lock<std::mutex> lock(mtx);
//this guarantees the order in which are being executed
cvar.wait(lock, [=]{return index == next;});
std::cout<< index << std::endl;
++next;
cvar.notify_all();//wakes all the thread, only the one with index=next will be executed
return;
}
int main()
{
long n=50;
std::vector<std::thread> workers;
for (long i=0; i < n; ++i){
workers.emplace_back(std::thread (doit,i));
}
//this guarantees your threads are all finished at the end of this block
for (auto& t : workers) {
t.join();
}
return(0);
}

Why not keep it simple?
int main() {
long n = 50;
std::vector<std::thread> threads;
for (long i = 0; i < n; ++i)
threads.emplace_back([=]() { std::cout << i << std::endl; });
for (const auto& t : threads) {
t.join();
}
return 0;
}

Try this snippet: You shouldn't use mtx.unlock() and let the condition_variable do the job. Also use std::ref to pass the function parameter to the thread.
std::mutex mtx;
std::condition_variable cvar;
bool ready = true;
void doit(long index) {
std::unique_lock<std::mutex> lock(mtx);
cvar.wait(lock, [=] {return ready == true; });
ready = false;
std::cout << index << std::endl;
ready = true;
cvar.notify_all();
return;
}
int main()
{
long n = 50;
for (long i = 0; i < n; ++i)
std::thread(doit, std::ref(i)).detach();
std::this_thread::sleep_for(std::chrono::seconds(3));
return(0);
}

std:: unique_lock is an RAII object. Declare it in a scope and cast your cares to the wind. Here's the problem: After doit calls mtx.unlock(), occasionally the next statement cvar.notify_all() will immediately wake up the thread with (the new) next == index. That thread will acquire the mutex. When doit returns, the lock destructor attempts to release the mutex, but it's held by another thread. Disaster ensues. Here's how to doit():
void doit(long index) {
{
std::unique_lock<std::mutex> lock(mtx);
cvar.wait(lock, [=] {return index == next; });
++next;
std::cout << index << std::endl;
}
cvar.notify_all();
return;
}

Related

C++ multi-threading Error: Single producer Multiple consumer

I am trying to achieve Single producer Multiple consumer , however below code is not able to compile.
Can someone help with this error ? Also would it work to wake all threads from this pool & a random thread would be able to acquire the lock?
TIA
`
threadPool/main.cpp:4:
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/thread:364:17:
error: no matching constructor for initialization of '_Gp' (aka
'tuple<unique_ptrstd::__1::__thread_struct, void (TestClass::*)(),
TestClass>')
new _Gp(std::move(__tsp),
^ ~~~~~~~~~~~~~~~~~
ls/usr/bin/../include/c++/v1/type_traits:2422:12: error: call to implicitly-deleted copy constructor of 'typename
decay::type' (aka 'TestClass')
return _VSTD::forward<_Tp>(__t);
----------------------------------------------
#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <condition_variable>
using namespace std;
class TestClass{
public:
void producer(int i) {
unique_lock<mutex> lockGuard(mtx);
Q.push(i);
lockGuard.unlock();
cond.notify_all();
}
void consumer() {
unique_lock<mutex> lockGuard(mtx);
cond.wait(lockGuard, [this]() {
return !Q.empty();
});
cout<<this_thread::get_id();
cout<<Q.front()<<endl;
Q.pop();
lockGuard.unlock();
};
private:
mutex mtx;
condition_variable cond;
queue<int> Q;
};
int main() {
std::cout << "Hello, World!" << std::endl;
int MAX_THREADS = std::thread::hardware_concurrency()-1;
vector<thread> ThreadVector;
TestClass testObj;
for(int i=0; i<MAX_THREADS; i++){
ThreadVector.emplace_back(&TestClass::consumer, std::move(testObj));
cout<<"Pool threadID:" <<ThreadVector[i].get_id()<<endl;
}
TestClass testObj2;
for(int i=0; i<10; i++) {
testObj.producer(i);
}
for(auto &&t : ThreadVector) {
t.join();
}
return 0;
}
`
Another version to call threads
int main()
{
std::vector<std::thread> vecOfThreads;
std::function<void(TestClass&)> func = [&](TestClass &obj) {
while(1) {
obj.consumer();
}
};
unsigned MAX_THREADS = std::thread::hardware_concurrency()-1;
TestClass obj;
for(int i=0; i<MAX_THREADS; i++) {
std::thread th1(func, std::ref(obj));
vecOfThreads.emplace_back(std::move(th1));
}
TestClass prod;
for(int i=0; i<10; i++) {
prod.producer(i);
}
for (std::thread & th : vecOfThreads)
{
if (th.joinable())
th.join();
}
return 0;
}
std::move(testObj) should be &testObj (a pointer to the object to call consumer on) - or std::ref(testobj) (which becomes a reference_wrapper (holding a pointer to the object too).
You should call produce at least as many times as you have threads, or else the program won't finish.
You don't need to unlock manually. The guards unlock automatically when they go out of scope.
Example:
class TestClass {
public:
void producer(int i) {
lock_guard<mutex> lockGuard(mtx); // here a lock_guard is enough
Q.push(i);
// no manual unlocking
cond.notify_all();
}
void consumer() {
unique_lock<mutex> lockGuard(mtx);
cond.wait(lockGuard, [this] { return !Q.empty(); });
cout << this_thread::get_id();
cout << Q.front() << endl;
Q.pop();
};
private:
mutex mtx;
condition_variable cond;
queue<int> Q;
};
int main() {
std::cout << "Hello, World!" << std::endl;
unsigned MAX_THREADS = std::thread::hardware_concurrency() - 1;
vector<thread> ThreadVector;
ThreadVector.reserve(MAX_THREADS); // since you know how many, reserve
TestClass testObj;
for(unsigned i = 0; i < MAX_THREADS; i++) {
// here, &testobj
ThreadVector.emplace_back(&TestClass::consumer, &testObj);
cout << "Pool threadID:" << ThreadVector[i].get_id() << endl;
}
// produce MAX_THREADS of things to put in the queue:
for(int i = 0; i < MAX_THREADS; i++) {
testObj.producer(i);
}
for(auto&& t : ThreadVector) {
t.join();
}
}
Regarding your questions in the comment section: If you'd like to keep the consumer threads running until you tell them to quit, you could add another variable (called run here) that the consumer threads monitor.
Example:
#include <condition_variable>
#include <iostream>
#include <queue>
#include <thread>
#include <vector>
using namespace std;
class TestClass {
public:
void producer(int i) {
lock_guard<mutex> lockGuard(mtx);
Q.push(i);
to_pool.notify_one();
}
void consumer() {
while(true) {
unique_lock<mutex> lockGuard(mtx);
to_pool.wait(lockGuard, [this] { return !run || !Q.empty(); });
if(!run) break; // time to quit
cout << this_thread::get_id() << ' ' << Q.front() << endl;
Q.pop();
// Tell producer that we picked one from the queue.
// if it's only interesting to notify when the queue is empty,
// add: if(Q.empty())
to_producer.notify_one();
}
};
void stop() {
lock_guard<mutex> lockGuard(mtx);
run = false; // tell all pool threads to quit
to_pool.notify_all();
}
void wait_for_all_work_to_be_done() {
std::unique_lock<mutex> lg(mtx);
to_producer.wait(lg, [this] { return Q.empty(); });
}
private:
bool run = true;
mutex mtx;
condition_variable to_pool;
condition_variable to_producer;
queue<int> Q;
};
int main() {
std::cout << "Hello, World!" << std::endl;
unsigned MAX_THREADS = std::thread::hardware_concurrency() - 1;
vector<thread> ThreadVector;
ThreadVector.reserve(MAX_THREADS);
TestClass testObj;
for(unsigned i = 0; i < MAX_THREADS; i++) {
ThreadVector.emplace_back(&TestClass::consumer, &testObj);
cout << "Pool threadID:" << ThreadVector[i].get_id() << endl;
}
for(int i = 0; i < MAX_THREADS / 2; i++) {
testObj.producer(i);
}
testObj.wait_for_all_work_to_be_done();
// stop pool threads
testObj.stop();
for(auto&& t : ThreadVector) t.join();
}

Tried to implement Leetcode's FizzBuzz multithreading question from scratch. Getting "libc++abi.dylib: terminating" error

I started learning multithreading a few days ago because of a coming internship interview. I learned it and tried to solve Leetcode's FizzBuzz multithreading question which I could solve successfully. But then I remembered that in the first interview I was asked to code from scratch and so I tried to implement the problem. I searched the error online but couldn't figure it out.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class FizzBuzz
{
private:
int n;
int currentNum;
std::mutex mu;
std::condition_variable cond;
public:
FizzBuzz(int n)
{
this->n = n;
currentNum = 1;
}
void fizz();
void buzz();
void fizzbuzz();
void number();
};
void FizzBuzz::fizz()
{
while(currentNum <= n)
{
std::unique_lock<std::mutex> locker(mu);
if (currentNum%3==0 && currentNum%5!=0)
{
std::cout << "Fizz" <<std::endl;
currentNum++;
locker.unlock();
cond.notify_all();
}
else
{
cond.wait(locker);
}
}
}
void FizzBuzz::buzz()
{
while(currentNum <= n)
{
std::unique_lock<std::mutex> locker(mu);
if (currentNum%3!=0 && currentNum%5==0)
{
std::cout << "Buzz" <<std::endl;
currentNum++;
locker.unlock();
cond.notify_all();
}
else
{
cond.wait(locker);
}
}
}
void FizzBuzz::fizzbuzz()
{
while(currentNum <= n)
{
std::unique_lock<std::mutex> locker(mu);
if (currentNum%3==0 && currentNum%5==0)
{
std::cout << "FizzBuzz" <<std::endl;
currentNum++;
locker.unlock();
cond.notify_all();
}
else
{
cond.wait(locker);
}
}
}
void FizzBuzz::number()
{
while(currentNum <= n)
{
std::unique_lock<std::mutex> locker(mu);
if (currentNum%3==0 && currentNum%5!=0)
{
std::cout << currentNum <<std::endl;
currentNum++;
locker.unlock();
cond.notify_all();
}
else
{
cond.wait(locker);
}
}
}
int main()
{
FizzBuzz fb(15);
std::thread t1 (&FizzBuzz::fizz, &fb);
std::thread t2 (&FizzBuzz::buzz, &fb);
std::thread t3 (&FizzBuzz::fizzbuzz, &fb);
std::thread t4 (&FizzBuzz::number, &fb);
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}
If you have other tips for me to improve myself, let me know. I'm new to multithreading. Trying to learn as much as possible.
EDIT: Added the join() statements for all the threads. Now it's getting into a deadlock.
In your FizzBuzz::number() the first if condition should be:
"if (currentNum%3!=0 && currentNum%5!=0)"
not
"if (currentNum%3==0 && currentNum%5!=0)"
You are not calling join() on any of your threads. This will cause the application to terminate when the std::thread objects are destroyed.
Add
t4.join();
t3.join();
t2.join();
t1.join();
to the end of your main function. This will wait until the threads finish before exiting the program.
Consider what happens when currentNum = 1 (your first value): currentNum % 3 == 0 and currentNum % 5 == 0 are both false. All threads end up waiting forever.
You should add this condition to some thread:
if (currentNum % 3 != 0 && currentNum % 5 != 0)
Change the condition for the function printing number
if (currentNum % 3 != 0 && currentNum % 5 != 0) {
...
}
it works

C++ Multiple consumer threads stuck on condition variable

I'm making a single producer, multiple consumers program in C++. I begin by calling consumer threads and then I add elements to an array.
Everything works fine, but in the end the consumer threads are not joining, because they're stuck waiting on condition variable and the program freezes.
I think the problem is that threads are constantly called in the loop because currentSize is not protected and they just can't exit out of the condition variable, but I don't know how to fix it.
struct Item {
public:
string name;
int time;
double height;
};
struct Monitor {
private:
Item items[12];
int currentSize;
bool finished;
mutex lock;
condition_variable cv;
public:
Monitor() {
finished = false;
currentSize = 0;
}
void put(Item item) {
unique_lock<mutex> guard(lock);
cv.wait(guard, [&] { return (currentSize < 12); });
items[currentSize] = item;
currentSize++;
cv.notify_all();
}
Item get() {
unique_lock<mutex> guard(lock);
cv.wait(guard, [&] { return (currentSize > 0); });
Item item = items[currentSize - 1];
currentSize--;
return item;
}
bool get_finished() {
return finished;
}
void set_finished() {
finished = true;
}
int get_size() {
return currentSize;
}
};
int main() {
vector<Item> items = read_file(file);
Monitor monitor;
vector<thread> threads;
vector<Item> results;
for (int i = 0; i < 4; i++) {
threads.emplace_back([&] {
while (!monitor.get_finished()) {
if (monitor.get_size() > 0) {
Item item = monitor.get();
results.push_back(item);
}
}
});
}
for (int i = 0; i < items.size(); i++) {
monitor.put(items[i]);
}
monitor.set_finished();
for_each(threads.begin(), threads.end(), mem_fn(&thread::join));
return 0;
}
Why the consumer threads block?
I have tested your code, and it turns out to be the producer thread blocking on the put() method. Why?
Imagine the following scenario: there are 13 items in the vector items.
The main thread (producer) happily loads the first 12 items, and waits on cv for the currentSize to become lower than 12.
The consumer threads are notified, and happily consume the first 12 items, and then wait on cv for currentSize to become greater than 0.
But wait! Now everyone is waiting on something, with no one notifying. Thus, all threads would block. You need to notify the producer when currentSize becomes lower than 12.
I noticed a few issues. made the member variables atomic, notify_all in get api. However there was al logic error as well. Imagine that you have 4 threads currently running and 5 items were in queue. At this point lets say each of the thread is able to get one out of the queue and now there are 4 threads and only one item in the queue. One of the thread takes the last one out and now there is 0 items in there however other three threads still waiting on the condition variable. So a solution is if the last item is out everythread should be notified and if there is no other elemnet get back from the API.
#include <iostream>
#include <vector>
#include <condition_variable>
#include <thread>
#include <algorithm>
#include <atomic>
using namespace std;
using Item = int;
struct Monitor {
private:
Item items[12];
std::atomic<int> currentSize;
std::atomic<bool> finished;
mutex lock;
condition_variable cv;
public:
Monitor() {
finished = false;
currentSize = 0;
}
void put(Item item) {
unique_lock<mutex> guard(lock);
cv.wait(guard, [&] { return (currentSize < 12); });
items[currentSize] = item;
currentSize++;
cv.notify_all();
std::cerr << "+ " << currentSize << std::endl ;
}
Item get() {
unique_lock<mutex> guard(lock);
cv.wait(guard, [&] { return (currentSize >= 0 ); });
Item item;
if (currentSize > 0 ){
currentSize--;
item = items[currentSize];
cv.notify_all();
std::cerr << "- " << currentSize << std::endl ;
}
return item;
}
bool get_finished() {
return finished;
}
void set_finished() {
finished = true;
}
int get_size() {
return currentSize;
}
};
int main() {
vector<Item> items(200);
std::fill ( items.begin() , items.end(), 100);
Monitor monitor;
vector<thread> threads;
vector<Item> results;
for (int i = 0; i < 10; i++) {
threads.emplace_back([&] {
while ( !monitor.get_finished() ) {
if (monitor.get_size() > 0) {
Item item = monitor.get();
results.push_back(item);
}
}
});
}
for (int i = 0; i < items.size(); i++) {
monitor.put(items[i]);
}
monitor.set_finished();
for_each(threads.begin(), threads.end(), mem_fn(&thread::join));
return 0;
}

Why program works correctly while join only 1 thread but there are 5

This is my code and I accidentally made a mistake buy not making the for loop longer but the code works as intended.
What happens after the program is completed? Does it have any fail cases or does the computer auto kill all thread and if there would be any additional code it Threads again would there be problems( for example if i would initiate 2 thread then there would be 6 thread working and the new thread ids would be 5 and 7?)
#include <iomanip>
#include <thread>
#include <iostream>
#include <mutex>
#include <sstream>
#include <vector>
#include <conio.h>
using namespace std;
bool endProgram = false;
struct Monitorius {
public:
int IOCounter = 0;
int readCounterC = 0;
int readCounterD = 0;
condition_variable cv;
mutex mtx;
int c = 10;
int d = 100;
Monitorius() {
c = 10;
d = 100;
IOCounter = 0;
readCounterC = 0;
readCounterD = 0;
}
void changeC(int i) {
while (!endProgram) {
unique_lock<mutex> lck(mtx);
cv.wait(lck, [&] {return readCounterC > 1; });
if (!endProgram) {
c += i;
readCounterC = 0;
cv.notify_all();
}
}
}
void changeD(int i) {
while (!endProgram) {
unique_lock<mutex> lck(mtx);
cv.wait(lck, [&] {return readCounterD > 1; });
if (!endProgram) {
d -= i;
readCounterD = 0;
cv.notify_all();
}
}
}
void readCD(int i) {
int oldC = -1;
int oldD = -1;
while (!endProgram) {
unique_lock<mutex> lck(mtx);
cv.wait(lck, [&] {return oldC != c && oldD != d; });
if (!endProgram) {
stringstream str;
str << i << ": c:" << c << " d: " << d << endl;
cout << str.str();
readCounterC++;
readCounterD++;
IOCounter++;
if (IOCounter >= 15)
endProgram = true;
cv.notify_all();
oldC = c;
oldD = d;
}
}
}
};
int main()
{
Monitorius M;
vector<thread> myThreads;
myThreads.reserve(5);
myThreads.emplace_back([&] { M.changeC(1); });
myThreads.emplace_back([&] { M.changeD(2); });
myThreads.emplace_back([&] { M.readCD(3); });
myThreads.emplace_back([&] { M.readCD(4); });
myThreads.emplace_back([&] { M.readCD(5); });
for (size_t i = 0; i < 1; i++)
myThreads[i].join();
_getch();
}
When your main function exits, all the threads in the vector will be destructed.
If they are not joined at that time std::terminate should be called by the std::thread destructor.
By detaching threads the thread-objects can be destructed and the thread still continues to run. But then on the common modern operating systems when the process ends (which happens after main have returned or exit is called) the threads will be killed anyway. To let threads continue running even after the "main" thread ends, you have to call a system-dependent function to exit the "main" thread.
I do not know if it's possible to do this on Windows though, as the ExitThread function should not be used by C++ code as it exits the thread without destructing objects.
The solution to both problems is of course to properly join all threads.

why introducing sleep causes producer consumer to wait?

I'm just learning to use the C++ threading library.
If anyone is curious - my code is just a modified version in a tutorial https://www.youtube.com/watch?v=13dFggo4t_I&t=6m45s
I wrote a simple producer/consumer code. I tried to introduce a sleep to make the producer and consumer in lockstep fashion, so item is consumed as soon as it is produced. But making the producer waits causes some deadlock. I couldn't figure out why.
Can you please help me point out what I'm missing in the code?
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
#define BUFFER_SIZE 10
std::mutex mu;
std::condition_variable full,empty;
std::vector<int> vec;
int i=0;
std::chrono::milliseconds slp(10);
void produce()
{
while (i < 2*BUFFER_SIZE) {
std::unique_lock<std::mutex> locker(mu);
full.wait(locker, [] {return vec.size() != BUFFER_SIZE;});
vec.push_back(i++);
empty.notify_one();
//std::this_thread::sleep_for(slp); <--- introducing this causes program to hang.
}
}
void consume()
{
while(!vec.empty()) {
std::unique_lock<std::mutex> locker(mu);
empty.wait(locker, [] {return !vec.empty();});
std::cout << "Consumed:" << vec.back() <<"\n";
vec.pop_back();
full.notify_one();
}
}
int main() {
vec.reserve(BUFFER_SIZE*2);
std::thread producer(produce), consumer(consume);
producer.join();
consumer.join();
return 0;
}
EDIT:
void produce()
{
while (i < 2*BUFFER_SIZE) {
std::unique_lock<std::mutex> locker(mu);
full.wait(locker, [] {return vec.size() != BUFFER_SIZE;});
vec.push_back(i++);
locker.unlock();
empty.notify_one();
std::this_thread::sleep_for(slp);
}
}
void consume()
{
while(!vec.empty()) {
std::unique_lock<std::mutex> locker(mu);
empty.wait(locker, [] {return !vec.empty();});
std::cout << "Consumed:" << vec.back() <<"\n";
vec.pop_back();
locker.unlock();
full.notify_one();
}
}
You have a couple of problems:
The consumer is accessing vec without the mutex held:
while(!vec.empty()) {
This is easily addressed by ensuring that all acceses to vec are "inside" the mutex.
If the consumer gets ahead of the producer and manages to empty vec, it will exit early. You can fix this by using some other mechanism to indicate completion.
You are performing some processing/io/sleep with the mutex held, reducing possible concurrency. ideally you should only hold the mutex while accessing shared state.
const unsigned BUFFER_SIZE = 10;
std::mutex mu;
std::condition_variable full,empty;
std::vector<int> vec;
const std::chrono::milliseconds slp(10);
auto done = false;
void produce()
{
for (auto i = 0u; i < 2 * BUFFER_SIZE; ++i) {
std::unique_lock<std::mutex> locker(mu);
full.wait(locker, [] {return vec.size() < BUFFER_SIZE;});
auto was_empty = vec.empty();
vec.push_back(i);
locker.unlock();
// Only notify if the buffer was empty before the push_back
if (was_empty) {
empty.notify_all();
}
std::this_thread::sleep_for(slp);
}
}
void consume()
{
for (;;) {
std::unique_lock<std::mutex> locker(mu);
while (vec.empty()) {
if (done) {
return;
}
empty.wait(locker);
}
auto was_full = vec.size() >= BUFFER_SIZE;
auto value = vec.back();
vec.pop_back();
locker.unlock();
if (was_full) {
full.notify_all();
}
std::cout << "Consumed: " << value << '\n';
}
}
int main() {
vec.reserve(BUFFER_SIZE*2);
std::thread producer(produce), consumer(consume);
producer.join();
// Produce some more
producer = std::thread(produce);
producer.join();
// Produce A LOT more
std::vector<std::thread> many_producers(8);
for (auto&& t : many_producers) {
t = std::thread(produce);
}
for (auto && t : many_producers) {
t.join();
}
// Tell consumer we are done producing
{
std::lock_guard<std::mutex> lock(mu);
done = true;
}
empty.notify_one();
consumer.join();
}
See it live at Coliru.