blocking queue using c++ std queue - c++

I'm trying to implement a blocking queue with limited size, for one provider and multiple consumers. it is working well when the consumer is sleep()ing for 1 second, but hangs when there is no sleep.
what am I doing wrong?
here is my code:
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
using namespace std;
template <class T> class BlockingQueue: public queue<T> {
public:
BlockingQueue() {
queue<T>();
}
BlockingQueue(int size) {
maxSize = size;
queue<T>();
}
void push(T item) {
unique_lock<std::mutex> wlck(writerMutex);
while(Full())
isFull.wait(wlck);
queue<T>::push(item);
if(notEmpty())
isEmpty.notify_one();
}
bool notEmpty() {
return !queue<T>::empty();
}
bool Full(){
return queue<T>::size() >= maxSize;
}
T pop() {
unique_lock<std::mutex> lck(readerMutex);
popMutex.lock();
while(queue<T>::empty()) {
isEmpty.wait(lck);
}
T value = queue<T>::front();
queue<T>::pop();
if(!Full())
isFull.notify_all();
popMutex.unlock();
return value;
}
private:
int maxSize;
std::mutex readerMutex;
std::mutex popMutex;
std::mutex writerMutex;
condition_variable isFull;
condition_variable isEmpty;
};
void runProvider(BlockingQueue<int>* Q) {
int number=0;
while(1) {
Q->push(number);
cout<<"provide "<<number<<endl;
number++;
}
}
void runConsumer(int n,BlockingQueue<int>* Q) {
int number;
while(1) {
number = Q->pop();
cout<<"consume#"<<n<<": "<<number<<endl;
}
}
int main(int argc, char** argv) {
BlockingQueue<int> *Queue = new BlockingQueue<int>(10);
cout<<"starting provider"<<endl;
std:thread provider(runProvider, Queue);
sleep(1);
cout<<"starting consumer"<<endl;
std::thread consumer1(runConsumer, 1,Queue);
std::thread consumer2(runConsumer, 2,Queue);
provider.join();
delete(Queue);
return 0;
}

Here is my fixed code for blocking queue with multiple providers and multiple consumers and limited queue size:
template <class T> class BlockingQueue: public queue<T> {
public:
BlockingQueue(int size) {
maxSize = size;
}
void push(T item) {
unique_lock<std::mutex> wlck(writerMutex);
while(Full())
isFull.wait(wlck);
queue<T>::push(item);
isEmpty.notify_all();
}
bool notEmpty() {
return !queue<T>::empty();
}
bool Full(){
return queue<T>::size() >= maxSize;
}
T pop() {
unique_lock<std::mutex> lck(readerMutex);
while(queue<T>::empty()) {
isEmpty.wait(lck);
}
T value = queue<T>::front();
queue<T>::pop();
if(!Full())
isFull.notify_all();
return value;
}
private:
int maxSize;
std::mutex readerMutex;
std::mutex writerMutex;
condition_variable isFull;
condition_variable isEmpty;
};

Related

Thread pool not completing all tasks

I have asked a simpler version of this question before and got the correct answer: Thread pools not working with large number of tasks
Now I am trying to run tasks from an object of a class in parallel using a thread pool. My task is simple and only prints a number for that instance of class. I am expecting numbers 0->9 get printed but instead I get some numbers get printed more than once and some numbers not printed at all. Can anyone see what I am doing wrong with creating tasks in my loop?
#include "iostream"
#include "ThreadPool.h"
#include <chrono>
#include <thread>
using namespace std;
using namespace dynamicThreadPool;
class test {
int x;
public:
test(int x_in) : x(x_in) {}
void task()
{
cout << x << endl;
}
};
int main(void)
{
thread_pool pool;
for (int i = 0; i < 10; i++)
{
test* myTest = new test(i);
std::function<void()> myFunction = [&] {myTest->task(); };
pool.submit(myFunction);
}
while (!pool.isQueueEmpty())
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
cout << "waiting for tasks to complete" << endl;
}
return 0;
}
And here is my thread pool, I got this definition from "C++ Concurrency in Action" book:
#pragma once
#include <queue>
#include <future>
#include <list>
#include <functional>
#include <memory>
template<typename T>
class threadsafe_queue
{
private:
mutable std::mutex mut;
std::queue<T> data_queue;
std::condition_variable data_cond;
public:
threadsafe_queue() {}
void push(T new_value)
{
std::lock_guard<std::mutex> lk(mut);
data_queue.push(std::move(new_value));
data_cond.notify_one();
}
void wait_and_pop(T& value)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk, [this] {return !data_queue.empty(); });
value = std::move(data_queue.front());
data_queue.pop();
}
bool try_pop(T& value)
{
std::lock_guard<std::mutex> lk(mut);
if (data_queue.empty())
return false;
value = std::move(data_queue.front());
data_queue.pop();
return true;
}
bool empty() const
{
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
};
class join_threads
{
std::vector<std::thread>& threads;
public:
explicit join_threads(std::vector<std::thread>& threads_) : threads(threads_) {}
~join_threads()
{
for (unsigned long i = 0; i < threads.size(); i++)
{
if (threads[i].joinable())
{
threads[i].join();
}
}
}
};
class thread_pool
{
std::atomic_bool done;
threadsafe_queue<std::function<void()> > work_queue;
std::vector<std::thread> threads;
join_threads joiner;
void worker_thread()
{
while (!done)
{
std::function<void()> task;
if (work_queue.try_pop(task))
{
task();
}
else
{
std::this_thread::yield();
}
}
}
public:
thread_pool() : done(false), joiner(threads)
{
unsigned const thread_count = std::thread::hardware_concurrency();
try
{
for (unsigned i = 0; i < thread_count; i++)
{
threads.push_back(std::thread(&thread_pool::worker_thread, this));
}
}
catch (...)
{
done = true;
throw;
}
}
~thread_pool()
{
done = true;
}
template<typename FunctionType>
void submit(FunctionType f)
{
work_queue.push(std::function<void()>(f));
}
bool isQueueEmpty()
{
return work_queue.empty();
}
};
There's too much code to analyse all of it but you take a pointer by reference here:
{
test* myTest = new test(i);
std::function<void()> myFunction = [&] {myTest->task(); };
pool.submit(myFunction);
} // pointer goes out of scope
After that pointer has gone out of scope you will have undefined behavior if you later do myTest->task();.
To solve that immediate problem, copy the pointer and delete the object afterwards to not leak memory:
{
test* myTest = new test(i);
std::function<void()> myFunction = [=] {myTest->task(); delete myTest; };
pool.submit(myFunction);
}
I suspect this could be solved without using new at all, but I'll leave that up to you.

More threads allocated than wanted

I have implemented a thread pool in C++, in which I create Nthread workers to which I assign some jobs from a queue I keep pushing task to. When The queue is empty and/or when I say so, the threads stop working. Everything runs on WSL (Ubuntu 20.04 Focal).
If I open Windows task manager when I launch the program, the number of threads actually working is more than the ones allocated. For example, if I run the program with 4 threads on a 12 cores machine, I can see at least 6 cores with above-average activity; if I use 10 threads, all 12 cores go to 100%. Is this behavior somehow expected or am I doing something wrong? I would expect to see one more thread than the ones allocated because I spawn Nthread threads from the main one (which by the way should stay quiet waiting for the others to finish...), but I cannot explain what I see.
I want to stress that I create all the Nthread threads before all operations, then I populate and process the queue and finally I destroy the threads, i.e. as far as I can see I do not create/destroy threads continouosly during the calculations.
EDIT
I forgot to mention I operate under C++11.
Here is the relevant C++ code.
In main.cc
ThreadPool *pool = new ThreadPool(fNThreads);
std::vector<std::function<void(int)>> *caller =
new std::vector<std::function<void(int)>>;
for (size_t iter = 0; iter < nIter; ++iter)
{
pool->ResetQueue();
for (size_t j = 0; nmax < 2; ++j)
{
caller->push_back(
[=](int iThr){function(iter, j, iThr);});
pool->PushTask((*caller)[j]);
}
pool->WaitForCompletion(1.e-4);
caller->clear();
}
delete caller;
delete pool;
SynchronizedQueue.hh
#ifndef SYNCQUEUE_H
#define SYNCQUEUE_H
#include <list>
#include <mutex>
#include <condition_variable>
template<typename T>
class SynchronizedQueue
{
public:
SynchronizedQueue();
~SynchronizedQueue();
void Put(T const & data);
void Put(T const && data);
T Get();
size_t Size();
SynchronizedQueue(SynchronizedQueue const &) = delete;
SynchronizedQueue & operator=(SynchronizedQueue const &) = delete;
SynchronizedQueue(SynchronizedQueue&&) = delete;
SynchronizedQueue & operator=(SynchronizedQueue&&) = delete;
private:
std::list<T> queue;
std::mutex mut;
std::condition_variable condvar;
};
template<typename T>
SynchronizedQueue<T>::SynchronizedQueue()
{}
template<typename T>
SynchronizedQueue<T>::~SynchronizedQueue()
{}
template<typename T>
void SynchronizedQueue<T>::Put(T const & data)
{
std::unique_lock<std::mutex> lck(mut);
queue.push_back(data);
condvar.notify_one();
}
template<typename T>
T SynchronizedQueue<T>::Get()
{
std::unique_lock<std::mutex> lck(mut);
while (queue.empty())
{
condvar.wait(lck);
}
T result = queue.front();
queue.pop_front();
return result;
}
template<typename T>
size_t SynchronizedQueue<T>::Size()
{
std::unique_lock<std::mutex> lck(mut);
return queue.size();
}
#endif
ThreadPool.hh
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include "SynchronizedQueue.hh"
#include <atomic>
#include <functional>
#include <mutex>
#include <thread>
#include <vector>
class ThreadPool
{
public:
ThreadPool(unsigned int nThreads = 1);
virtual ~ThreadPool();
void PushTask(std::function<void(int)> func);
void WaitForCompletion();
void WaitForCompletion(int sec);
void ResetQueue();
void JoinThreads();
void Delay(int sec);
size_t GetWorkQueueLength();
private:
void WorkerThread(int i);
std::atomic<bool> done;
unsigned int threadCount;
SynchronizedQueue<std::function<void(int)>> workQueue;
std::vector<std::thread> threads;
};
#endif
ThreadPool.cc
#include "ThreadPool.hh"
#include "SynchronizedQueue.hh"
#include <chrono>
//#include <iostream>
void doNothing(int i)
{}
ThreadPool::ThreadPool(unsigned int nThreads)
: done(false)
{
if (nThreads <= 0)
{
threadCount = std::thread::hardware_concurrency();
}
else
{
threadCount = nThreads;
}
for (unsigned int i = 0; i < threadCount; ++i)
{
threads.push_back(std::thread(&ThreadPool::WorkerThread, this, i));
}
}
ThreadPool::~ThreadPool()
{
WaitForCompletion();
JoinThreads();
}
void ThreadPool::WaitForCompletion(int sec)
{
if (!done)
{
while (GetWorkQueueLength())
{
std::this_thread::sleep_for(std::chrono::seconds(sec));
}
done = true;
for (unsigned int i = 0; i < threadCount; ++i)
{
PushTask(&doNothing);
}
}
}
void ThreadPool::WaitForCompletion()
{
if (!done)
{
while (GetWorkQueueLength())
{}
done = true;
for (unsigned int i = 0; i < threadCount; ++i)
{
PushTask(&doNothing);
}
}
}
void ThreadPool::JoinThreads()
{
for (auto& th : threads)
{
if (th.joinable())
{
th.join();
}
}
}
void ThreadPool::Delay(int sec)
{
std::this_thread::sleep_for(std::chrono::seconds(sec));
}
void ThreadPool::PushTask(std::function<void(int)> func)
{
workQueue.Put(func);
}
void ThreadPool::ResetQueue()
{
done = false;
}
void ThreadPool::WorkerThread(int i)
{
while (!done)
{
workQueue.Get()(i);
}
}
size_t ThreadPool::GetWorkQueueLength()
{
return workQueue.Size();
}

Multi-threaded copying files using queue

The task:
I have many (500) .txt files. Each file contains other filenames. I have a filename to begin with. I need to copy that file and all files mentioned in it, and so on, to a subdirectory copy/. The solution must be multi-threaded.
I wrote the following code, but it executes a bit faster with one thread than with two. What's my mistake and how can I change this?
#include <iostream>
#include <fstream>
#include <string>
#include <queue>
#include <set>
#include <cassert>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <thread>
template <typename T>
class UQueue {
private:
std::queue<T> m_queue;
std::set<T> m_set;
public:
bool push(const T& t) {
if (m_set.insert(t).second) {
m_queue.push(t);
return true;
}
return false;
}
void pop() {
assert(!m_queue.empty());
m_queue.pop();
}
const T& front() const {
return m_queue.front();
}
bool empty(){
return m_queue.empty();
}
int size_of_set(){
return m_set.size();
}
};
template <class T>
class SafeQueue {
public:
SafeQueue(void)
: q()
, m()
, c()
{}
~SafeQueue(void) {}
void enqueue(T t) {
std::lock_guard<std::mutex> lock(m);
q.push(t);
c.notify_one();
}
T dequeue(void) {
std::unique_lock<std::mutex> lock(m);
while(q.empty()) {
c.wait(lock);
}
T val = q.front();
q.pop();
return val;
}
int size_of_set() {
return q.size_of_set();
}
bool empty() {
return q.empty();
}
private:
UQueue<T> q;
mutable std::mutex m;
std::condition_variable c;
};
void copyFile(std::string fromFile, std::string toFile){
std::ifstream src(fromFile, std::ios::binary);
std::ofstream dst(toFile, std::ios::binary);
dst << src.rdbuf();
}
std::set<std::string> extract( std::string file_name ) {
std::ifstream file(file_name) ;
std::string line;
std::set<std::string> temp;
while (!file.eof()){
file >> line;
temp.insert(line);
}
return temp;
}
class Crawler{
public:
void init();
void work();
void thread_init();
int processed(){return q.size_of_set();};
Crawler(std::string start, int thr);
std::string target(){return m_conf.m_targetDir;};
private:
struct Config{
int m_threads;
std::string m_startFile = "1.txt";
std::string m_targetDir = "copy";
};
Config m_conf;
SafeQueue<std::string> q;
};
void Crawler::init(){
q.enqueue(m_conf.m_startFile);
}
Crawler::Crawler(std::string start, int thr){
m_conf.m_startFile = start;
m_conf.m_threads = thr;
}
void Crawler::work(){
std::string path;
std::set<std::string> tempSet;
while (!q.empty()){
path = q.dequeue();
copyFile(path, this->target() + "/" + path);
tempSet = extract(path);
for (auto f : tempSet){
q.enqueue(f);
}
}
}
void Crawler::thread_init(){
std::vector<std::thread> workers;
for (int i = 0; i < m_conf.m_threads; ++i){
workers.push_back(std::thread(&Crawler::work, this));
}
for (auto& thr : workers){
thr.join();
}
}
int main(){
Crawler c("1.txt", 2);
auto start = std::chrono::high_resolution_clock::now();
c.init();
c.thread_init();
auto end = std::chrono::high_resolution_clock::now();
std::cout << c.processed()<<
" " << std::chrono::duration<double, std::milli>(end - start).count() << " ms\n";
}

Sync queue between two threads

This is a simple program which has a function start() which waits for user to enter something(using infinite loop) and stores it in queue. start() runs in a separate thread. After user enters some value, the size of queue remains zero in main. How can the queue be synchronized?
code: source.cpp
#include <iostream>
#include "kl.h"
using namespace std;
int main()
{
std::thread t1(start);
while (1)
{
if (q.size() > 0)
{
std::cout << "never gets inside this if\n";
std::string first = q.front();
q.pop();
}
}
t1.join();
}
code: kl.h
#include <queue>
#include <iostream>
#include <string>
void start();
static std::queue<std::string> q;
code: kl.cpp
#include "kl.h"
using namespace std;
void start()
{
char i;
string str;
while (1)
{
for (i = 0; i <= 1000; i++)
{
//other stuff and str input
q.push(str);
}
}
}
Your code contains a race - by me it crashed; both threads are potentially modifying a shared queue. (Also, you're looping with char i for values up to 1000 - not a good idea, probably.)
You should protect your shared queue with a std::mutex, and use a std::condition_variable to notify that there is a reason to check the queue.
Specifically, you should consider the following (which is very common for your case of a producer consumer):
Access the queue only when holding the mutex.
Use the condition variable to notify that you've pushed something into it.
Use the condition variable to specify a condition on when there's a point to continue processing.
Here is a rewrite of your code:
#include <iostream>
#include <queue>
#include <thread>
#include <condition_variable>
#include <mutex>
using namespace std;
std::queue<std::string> q;
std::mutex m;
std::condition_variable cv;
void start()
{
string str;
for (std::size_t i = 0; i <= 1000; i++) {
//other stuff and str input
std::cout << "here" << std::endl;
std::unique_lock<std::mutex> lk(m);
q.push(str);
lk.unlock();
cv.notify_one();
}
}
int main()
{
std::thread t1(start);
for (std::size_t i = 0; i <= 1000; i++)
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return !q.empty();});
std::string first = q.front();
q.pop();
}
t1.join();
}
My synced queue class example and its usage:
template<typename T>
class SyncQueue
{
std::queue<T> m_Que;
std::mutex m_Lock;
std::condition_variable m_ConVar;
public:
void enque(T item)
{
std::unique_lock<std::mutex> lock(m_Lock);
m_Que.push(item);
lock.unlock();
m_ConVar.notify_all();
}
T deque()
{
std::unique_lock<std::mutex> lock(m_Lock);
do
{
m_ConVar.wait(lock);
} while(m_Que.size() == 0); // extra check from spontaneous notifications
auto ret = m_Que.front();
m_Que.pop();
return ret;
}
};
int main()
{
using namespace std::chrono_literals;
SyncQueue<int> sq;
std::thread consumer([&sq]()
{
std::cout << "consumer" << std::endl;
for(;;)
{
std::cout << sq.deque() << std::endl;
}
});
std::thread provider([&sq]()
{
std::this_thread::sleep_for(1s);
sq.enque(1);
std::this_thread::sleep_for(3s);
sq.enque(2);
std::this_thread::sleep_for(5s);
sq.enque(3);
});
consumer.join();
return 0;
}
/* Here I have a code snippate with Separate class for
Producing and Consuming along with buffer class */
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <deque>
#include <vector>
using namespace std;
mutex _mutex_1,_mutex_2;
condition_variable cv;
template <typename T>
class Queue
{
deque<T> _buffer;
const unsigned int max_size = 10;
public:
Queue() = default;
void push(const T& item)
{
while(1)
{
unique_lock<mutex> locker(_mutex_1);
cv.wait(locker,[this](){ return _buffer.size() < max_size; });
_buffer.push_back(item);
locker.unlock();
cv.notify_all();
return;
}
}
T pop()
{
while(1)
{
unique_lock<mutex> locker(_mutex_1);
cv.wait(locker,[this](){ return _buffer.size() > 0; });
int back = _buffer.back();
_buffer.pop_back();
locker.unlock();
cv.notify_all();
return back;
}
}
};
class Producer
{
Queue<int>* _buffer;
public:
Producer(Queue<int>* _buf)
{
this->_buffer = _buf;
}
void run()
{
while(1)
{
auto num = rand()%100;
_buffer->push(num);
_mutex_2.lock();
cout<<"Produced:"<<num<<endl;
this_thread::sleep_for(std::chrono::milliseconds(50));
_mutex_2.unlock();
}
}
};
class Consumer
{
Queue<int>* _buffer;
public:
Consumer(Queue<int>* _buf)
{
this->_buffer = _buf;
}
void run()
{
while(1)
{
auto num = _buffer->pop();
_mutex_2.lock();
cout<<"Consumed:"<<num<<endl;
this_thread::sleep_for(chrono::milliseconds(50));
_mutex_2.unlock();
}
}
};
void client()
{
Queue<int> b;
Producer p(&b);
Consumer c(&b);
thread producer_thread(&Producer::run, &p);
thread consumer_thread(&Consumer::run, &c);
producer_thread.join();
consumer_thread.join();
}
int main()
{
client();
return 0;
}

C++ Equivalent to Java's BlockingQueue

I'm in the process of porting some Java code over to C++, and one particular section makes use of a BlockingQueue to pass messages from many producers to a single consumer.
If you are not familiar with what a Java BlockingQueue is, it is just a queue that has a hard capacity, which exposes thread safe methods to put() and take() from the queue. put() blocks if the queue is full, and take() blocks if the queue is empty. Also, timeout-sensitive versions of these methods are supplied.
Timeouts are relevant to my use-case, so a recommendation that supplies those is ideal. If not, I can code up some myself.
I've googled around and quickly browsed the Boost libraries and I'm not finding anything like this. Maybe I'm blind here...but does anyone know of a good recommendation?
Thanks!
It isn't fixed size and it doesn't support timeouts but here is a simple implementation of a queue I had posted recently using C++ 2011 constructs:
#include <mutex>
#include <condition_variable>
#include <deque>
template <typename T>
class queue
{
private:
std::mutex d_mutex;
std::condition_variable d_condition;
std::deque<T> d_queue;
public:
void push(T const& value) {
{
std::unique_lock<std::mutex> lock(this->d_mutex);
d_queue.push_front(value);
}
this->d_condition.notify_one();
}
T pop() {
std::unique_lock<std::mutex> lock(this->d_mutex);
this->d_condition.wait(lock, [=]{ return !this->d_queue.empty(); });
T rc(std::move(this->d_queue.back()));
this->d_queue.pop_back();
return rc;
}
};
It should be trivial to extend and use a timed wait for popping. The main reason I haven't done it is that I'm not happy with the interface choices I have thought of so far.
Here's an example of a blocking queue with shutdown request feature:
template <typename T> class BlockingQueue {
std::condition_variable _cvCanPop;
std::mutex _sync;
std::queue<T> _qu;
bool _bShutdown = false;
public:
void Push(const T& item)
{
{
std::unique_lock<std::mutex> lock(_sync);
_qu.push(item);
}
_cvCanPop.notify_one();
}
void RequestShutdown() {
{
std::unique_lock<std::mutex> lock(_sync);
_bShutdown = true;
}
_cvCanPop.notify_all();
}
bool Pop(T &item) {
std::unique_lock<std::mutex> lock(_sync);
for (;;) {
if (_qu.empty()) {
if (_bShutdown) {
return false;
}
}
else {
break;
}
_cvCanPop.wait(lock);
}
item = std::move(_qu.front());
_qu.pop();
return true;
}
};
U should write the class of semephore first
#ifndef SEMEPHORE_H
#define SEMEPHORE_H
#include <mutex>
#include <condition_variable>
class semephore {
public:
semephore(int count = 0)
: count(count),
m(),
cv()
{
}
void await() {
std::unique_lock<std::mutex> lk(m);
--count;
if (count < 0) {
cv.wait(lk);
}
}
void post() {
std::unique_lock<std::mutex> lk(m);
++count;
if (count <= 0) {
cv.notify_all();
}
}
private:
int count;
std::mutex m;
std::condition_variable cv;
};
#endif // SEMEPHORE_H
now the blocked_queue can use the semephore to deal with it
#ifndef BLOCKED_QUEUE_H
#define BLOCKED_QUEUE_H
#include <list>
#include "semephore.h"
template <typename T>
class blocked_queue {
public:
blocked_queue(int count)
: s_products(),
s_free_space(count),
li()
{
}
void put(const T &t) {
s_free_space.await();
li.push_back(t);
s_products.post();
}
T take() {
s_products.await();
T res = li.front();
li.pop_front();
s_free_space.post();
return res;
}
private:
semephore s_products;
semephore s_free_space;
std::list<T> li;
};
#endif // BLOCKED_QUEUE_H
OK I'm a bit late to the party but I think this is a better fit for the Java's BlockingQueue implementation. Here I too use one mutex and two conditions to look after not full and not empty. IMO a BlockingQueue makes more sense with limited capacity which I didn't see in the other answers. I include a simple test scenario too:
#include <iostream>
#include <algorithm>
#include <queue>
#include <mutex>
#include <thread>
#include <condition_variable>
template<typename T>
class blocking_queue {
private:
size_t _capacity;
std::queue<T> _queue;
std::mutex _mutex;
std::condition_variable _not_full;
std::condition_variable _not_empty;
public:
inline blocking_queue(size_t capacity) : _capacity(capacity) {
// empty
}
inline size_t size() const {
std::unique_lock<std::mutex> lock(_mutex);
return _queue.size();
}
inline bool empty() const {
std::unique_lock<std::mutex> lock(_mutex);
return _queue.empty();
}
inline void push(const T& elem) {
{
std::unique_lock<std::mutex> lock(_mutex);
// wait while the queue is full
while (_queue.size() >= _capacity) {
_not_full.wait(lock);
}
std::cout << "pushing element " << elem << std::endl;
_queue.push(elem);
}
_not_empty.notify_all();
}
inline void pop() {
{
std::unique_lock<std::mutex> lock(_mutex);
// wait while the queue is empty
while (_queue.size() == 0) {
_not_empty.wait(lock);
}
std::cout << "popping element " << _queue.front() << std::endl;
_queue.pop();
}
_not_full.notify_one();
}
inline const T& front() {
std::unique_lock<std::mutex> lock(_mutex);
// wait while the queue is empty
while (_queue.size() == 0) {
_not_empty.wait(lock);
}
return _queue.front();
}
};
int main() {
blocking_queue<int> queue(5);
// create producers
std::vector<std::thread> producers;
for (int i = 0; i < 10; i++) {
producers.push_back(std::thread([&queue, i]() {
queue.push(i);
// produces too fast
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}));
}
// create consumers
std::vector<std::thread> consumers;
for (int i = 0; i < 10; i++) {
producers.push_back(std::thread([&queue, i]() {
queue.pop();
// consumes too slowly
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}));
}
std::for_each(producers.begin(), producers.end(), [](std::thread &thread) {
thread.join();
});
std::for_each(consumers.begin(), consumers.end(), [](std::thread &thread) {
thread.join();
});
return EXIT_SUCCESS;
}