Consider following example -
#include <boost/thread.hpp>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
void wait(int seconds)
{
boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}
boost::shared_mutex mutex;
std::vector<int> random_numbers;
void fill()
{
std::srand(static_cast<unsigned int>(std::time(0)));
for (int i = 0; i < 3; ++i)
{
boost::unique_lock<boost::shared_mutex> lock(mutex);
random_numbers.push_back(std::rand());
lock.unlock();
wait(1);
}
}
void print()
{
for (int i = 0; i < 3; ++i)
{
wait(1);
boost::shared_lock<boost::shared_mutex> lock(mutex);
std::cout << random_numbers.back() << std::endl;
}
}
int sum = 0;
void count()
{
for (int i = 0; i < 3; ++i)
{
wait(1);
boost::shared_lock<boost::shared_mutex> lock(mutex);
sum += random_numbers.back();
}
}
int main()
{
boost::thread t1(fill);
boost::thread t2(print);
boost::thread t3(count);
t1.join();
t2.join();
t3.join();
std::cout << "Summe: " << sum << std::endl;
}
In the given example, both print() and count() access random_numbers read-only. While the print() function writes the last number of random_numbers to the standard output stream, the count() function adds it to the variable sum. Since neither function modifies random_numbers, both can access it at the same time using a non-exclusive lock of type boost::shared_lock.
My question is : As the resource is read only why the shared mutex is needed at the first place in count and print function?' Cant we manage without it?
As the resource is read only [...]
No, it is not : the fill() method proceed to writes through the following :
random_numbers.push_back(std::rand()); // write to random_numbers
So the shared mutex really is necessary to synchronize your access to the vector.
Related
As per cppreference document:
All member functions (including copy constructor and copy assignment)
can be called by multiple threads on different instances of shared_ptr
without additional synchronization even if these instances are copies
and share ownership of the same object.
As i understood from cppreference, you don't need to put synchronization as you are calling different member functions using different instances of shared_ptr which points to the same object.
Please correct me if i understood wrong.and also give small example to understand it clearly.
#include <iostream>
#include <memory>
#include <thread>
using namespace std;
class Demo
{
public:
int Value;
Demo():Value(10){}
void fun1()
{
for(int i=0; i<300000; i++)
{
Value = Value + i;
std::cout << "Value1 :" << Value << std::endl;
}
}
void fun2()
{
for(int i=0; i<300000; i++)
{
Value = Value + i;
std::cout << "Value2 :" << Value << std::endl;
}
}
void fun3()
{
for(int i=0; i<300000; i++)
{
Value = Value + i;
std::cout << "Value3 :" << Value << std::endl;
}
}
};
int main()
{
std::shared_ptr<Demo> ptr1(new Demo);
std::thread t1(&Demo::fun1, ptr1);
std::shared_ptr<Demo> ptr2(ptr1);
std::thread t2(&Demo::fun2, ptr2);
std::shared_ptr<Demo> ptr3(ptr2);
std::thread t3(&Demo::fun3, ptr3);
t1.join();
t2.join();
t3.join();
}
//output:
Getting random(asynchronized) output as shown below:
Value3 :70993659Value2 :
71000412
Value1 :71006910Value2 :
70993659Value1 :71013664
All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object.
shared_ptr's member functions can be called without synchronization. However, you still need to synchronize member function calls of the template type, i.e. Demo.
fun1, fun2 and fun3 are member of Demo, not member of shared_ptr. So you still need to use lock to protect them.
Yes, you need synchronization for both value_ and std::cout.
Beside that, the sum of the first 300,000 positive integers is: 45,000,000,000 (45 billion). X 3 threads is: 13.5 billion. To avoid undefined behavior when a signed integer exceeds INT_MAX (2,147,483,647), it is advisable to change its data type to unsigned integer and to use wider integer data types such as uint32_t and uint64_t.
Example:
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
class Demo
{
public:
uint64_t value_;
Demo() : value_(10) {}
void fun1()
{
for (uint32_t i = 0; i < 300000; i++)
{
std::lock_guard lock(mutex_);
value_ += i;
std::cout << "Value1 :" << value_ << std::endl;
}
}
void fun2()
{
for (uint32_t i = 0; i < 300000; i++)
{
std::lock_guard lock(mutex_);
value_ += i;
std::cout << "Value2 :" << value_ << std::endl;
}
}
void fun3()
{
for (uint32_t i = 0; i < 300000; i++)
{
std::lock_guard lock(mutex_);
value_ += i;
std::cout << "Value3 :" << value_ << std::endl;
}
}
protected:
std::mutex mutex_{};
};
int main()
{
std::shared_ptr<Demo> ptr1(new Demo);
std::thread t1(&Demo::fun1, ptr1);
std::shared_ptr<Demo> ptr2(ptr1);
std::thread t2(&Demo::fun2, ptr2);
std::shared_ptr<Demo> ptr3(ptr2);
std::thread t3(&Demo::fun3, ptr3);
t1.join();
t2.join();
t3.join();
}
In C++ how i can write two parallel threads which will work one by one.For example in below code it need to print 0 t 100 sequentially.In below code the numbers are printing ,but all are not sequential.I need to print like 1,2,3,4,5,6.....99.If any body know , try to add with sample code also.
#pragma once
#include <mutex>
#include <iostream>
#include <vector>
#include <thread>
#include <condition_variable>
using namespace std;
class CuncurrentThread
{
public:
mutex mtx;
condition_variable cv;
static bool ready;
static bool processed;
void procThread1()
{
for (int i = 0; i < 100; i += 2)
{
unique_lock<mutex> lk(mtx);
cv.notify_one();
if(lk.owns_lock())
cv.wait(lk);
cout << "procThread1 : " << i << "\n";
lk.unlock();
cv.notify_one();
}
};
void procThread2()
{
for (int i = 1; i < 100; i += 2)
{
unique_lock<mutex> lk(mtx);
cv.notify_one();
if (lk.owns_lock())
cv.wait(lk);
cout << "procThread2 : " << i << "\n";
lk.unlock();
cv.notify_one();
}
};
static void ThreadDriver(CuncurrentThread* thr)
{
vector<thread> threads;
threads.push_back(thread(&CuncurrentThread::procThread1, thr));
threads.push_back(thread(&CuncurrentThread::procThread2, thr));
for (auto& thread : threads)
thread.join();
};
};
bool CuncurrentThread::ready = false;
int main()
{
CuncurrentThread tr;
CuncurrentThread::ThreadDriver(&tr);
}
Assuming you have a valid use case for using two threads like this, here is an example. I prefer using std::async over std::thread it has better abstraction and information exchange with the main thread.
The example is written for 2 threads but can easily be changed to more threads.
Live demo here : https://onlinegdb.com/eQex9o_nMz
#include <future>
#include <condition_variable>
#include <iostream>
// Setup a helper class that sets up
// the three things needed to correctly
// use a condition variable
// 1) a mutex
// 2) a variable
// 3) a condition_variable (which is more of a signal then a variable)
//
// also give this class some functions
// so the the code becomes more self-explaining
class thread_switcher_t
{
public:
void thread1_wait_for_turn()
{
std::unique_lock<std::mutex> lock{ m_mtx };
m_cv.wait(lock, [&] {return (thread_number==0); });
}
void thread2_wait_for_turn()
{
std::unique_lock<std::mutex> lock{ m_mtx };
m_cv.wait(lock, [&] {return (thread_number==1); });
}
void next_thread()
{
std::unique_lock<std::mutex> lock{ m_mtx };
thread_number = (thread_number + 1) % 2;
m_cv.notify_all();
}
private:
std::size_t thread_number{ 0 };
std::mutex m_mtx;
std::condition_variable m_cv;
};
int main()
{
thread_switcher_t switcher;
auto future1 = std::async(std::launch::async, [&]
{
for(std::size_t n = 0; n <= 100; n+=2)
{
switcher.thread1_wait_for_turn();
std::cout << "thread 1 : " << n << "\n";
switcher.next_thread();
}
});
auto future2 = std::async(std::launch::async, [&]
{
for (std::size_t n = 1; n <= 100; n += 2)
{
switcher.thread2_wait_for_turn();
std::cout << "thread 2 : " << n << "\n";
switcher.next_thread();
}
});
future1.get();
future2.get();
return 0;
}
You can use ready variable as a condition for condition variable.
#include <mutex>
#include <iostream>
#include <vector>
#include <thread>
#include <condition_variable>
using namespace std;
class CuncurrentThread
{
public:
mutex mtx;
condition_variable cv;
static bool ready;
//static bool processed;
void procThread1()
{
for (int i = 0; i < 100; i += 2)
{
unique_lock<mutex> lk(mtx);
// cv.notify_one();
// if(lk.owns_lock())
// wait until this condition is true i.e. until ready is false
cv.wait(lk, [&]() { return !ready; });
cout << "procThread1 : " << i << "\n";
// set ready to true and notify waiting thread
ready = true;
lk.unlock();
cv.notify_one();
}
};
void procThread2()
{
for (int i = 1; i < 100; i += 2)
{
unique_lock<mutex> lk(mtx);
// cv.notify_one();
// if (lk.owns_lock())
// wait until this condition is true i.e. until ready is true
cv.wait(lk, [&]() { return ready; });
cout << "procThread2 : " << i << "\n";
// set ready to false and notify waiting thread
ready = false;
lk.unlock();
cv.notify_one();
}
};
static void ThreadDriver(CuncurrentThread* thr)
{
vector<thread> threads;
threads.push_back(thread(&CuncurrentThread::procThread1, thr));
threads.push_back(thread(&CuncurrentThread::procThread2, thr));
for (auto& thread : threads)
thread.join();
};
};
bool CuncurrentThread::ready = false;
int main()
{
CuncurrentThread tr;
CuncurrentThread::ThreadDriver(&tr);
}
Link where I tested this: https://godbolt.org/z/4jEns16oq
I have simple class Hello and I am trying to call member function say_hello on different thread. I created two different implementation it hellos_in_stack and hellos_in_heap. hellos_in_heap works as expected however hellos_on_stack have a race condition on member variable _i. How can I avoid it on stack using mutex?
#include <thread>
#include <iostream>
#include <vector>
#include <mutex>
std::mutex mu;
class Hello
{
int _i;
public:
Hello()
{
std::lock_guard<std::mutex> lock(mu);
_i = 0;
}
~Hello(){
}
void say_hello()
{
std::lock_guard<std::mutex> lock(mu);
std::cout << "say_hello from thread " << ++_i << " " <<this << " " << std::this_thread::get_id() << std::endl;
}
};
void hellos_in_stack()
{
std::vector<std::thread> threads;
for(int i = 0; i < 4; ++i)
{
Hello h;
threads.push_back(std::thread(&Hello::say_hello, &h));
}
for(auto& thread : threads){
thread.join();
}
}
void hellos_in_heap()
{
std::vector<std::thread> threads;
std::vector<Hello *> hellos;
Hello *h = nullptr;
for(int i = 0; i < 4; ++i)
{
h = new Hello();
hellos.push_back(h);
threads.push_back(std::thread(&Hello::say_hello, h));
}
for(auto& thread : threads){
thread.join();
}
for(auto hello : hellos){
delete hello;
}
}
int main()
{
hellos_in_stack();
hellos_in_heap();
return 0;
}
Let's describe the race condition first...
The line Hello h; is constructing h on the main thread's stack. Once the for loop moves on to create the next thread, h is destroyed and another Hello is created -- likely, but not guaranteed, to be at the same address as the previous h.
h must be kept alive for the lifetime of the thread that is running its say_hello method.
One solution would be to create h on the new thread's stack. This can be done like so:
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i)
{
threads.emplace_back([]() {
Hello h;
h.say_hello();
});
}
Another option, if you still need the instances of h to be accessible from the main thread, would be to store them in a container.
std::vector<std::thread> threads;
std::list<Hello> hellos;
for (int i = 0; i < 4; ++i)
{
hellos.emplace_back();
threads.emplace_back(&Hello::say_hello, &hellos.back());
}
Using a container we've introduced some more complexity. Now, care must be taken to make sure that we use the container itself in a safe way. In this case std::list is used instead of std::vector because calling emplace_back/push_back on std::vector can cause it to resize its buffer. This would destroy Hello instance out from under running threads!
Running example: https://ideone.com/F7STsf
I have a list of objects, each object has member variables which are calculated by an "update" function. I want to update the objects in parallel, that is I want to create a thread for each object to execute it's update function.
Is this a reasonable thing to do? Any reasons why this may not be a good idea?
Below is a program which attempts to do what I described, this is a complete program so you should be able to run it (I'm using VS2015). The goal is to update each object in parallel. The problem is that once the update function completes, the thread throws an "resource dead lock would occur" exception and aborts.
Where am I going wrong?
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <thread>
#include <mutex>
#include <chrono>
class Object
{
public:
Object(int sleepTime, unsigned int id)
: m_pSleepTime(sleepTime), m_pId(id), m_pValue(0) {}
void update()
{
if (!isLocked()) // if an object is not locked
{
// create a thread to perform it's update
m_pThread.reset(new std::thread(&Object::_update, this));
}
}
unsigned int getId()
{
return m_pId;
}
unsigned int getValue()
{
return m_pValue;
}
bool isLocked()
{
bool mutexStatus = m_pMutex.try_lock();
if (mutexStatus) // if mutex is locked successfully (meaning it was unlocked)
{
m_pMutex.unlock();
return false;
}
else // if mutex is locked
{
return true;
}
}
private:
// private update function which actually does work
void _update()
{
m_pMutex.lock();
{
std::cout << "thread " << m_pId << " sleeping for " << m_pSleepTime << std::endl;
std::chrono::milliseconds duration(m_pSleepTime);
std::this_thread::sleep_for(duration);
m_pValue = m_pId * 10;
}
m_pMutex.unlock();
try
{
m_pThread->join();
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl; // throws "resource dead lock would occur"
}
}
unsigned int m_pSleepTime;
unsigned int m_pId;
unsigned int m_pValue;
std::mutex m_pMutex;
std::shared_ptr<std::thread> m_pThread; // store reference to thread so it doesn't go out of scope when update() returns
};
typedef std::shared_ptr<Object> ObjectPtr;
class ObjectManager
{
public:
ObjectManager()
: m_pNumObjects(0){}
void updateObjects()
{
for (int i = 0; i < m_pNumObjects; ++i)
{
m_pObjects[i]->update();
}
}
void removeObjectByIndex(int index)
{
m_pObjects.erase(m_pObjects.begin() + index);
}
void addObject(ObjectPtr objPtr)
{
m_pObjects.push_back(objPtr);
m_pNumObjects++;
}
ObjectPtr getObjectByIndex(unsigned int index)
{
return m_pObjects[index];
}
private:
std::vector<ObjectPtr> m_pObjects;
int m_pNumObjects;
};
void main()
{
int numObjects = 2;
// Generate sleep time for each object
std::vector<int> objectSleepTimes;
objectSleepTimes.reserve(numObjects);
for (int i = 0; i < numObjects; ++i)
objectSleepTimes.push_back(rand());
ObjectManager mgr;
// Create some objects
for (int i = 0; i < numObjects; ++i)
mgr.addObject(std::make_shared<Object>(objectSleepTimes[i], i));
// Print expected object completion order
// Sort from smallest to largest
std::sort(objectSleepTimes.begin(), objectSleepTimes.end());
for (int i = 0; i < numObjects; ++i)
std::cout << objectSleepTimes[i] << ", ";
std::cout << std::endl;
// Update objects
mgr.updateObjects();
int numCompleted = 0; // number of objects which finished updating
while (numCompleted != numObjects)
{
for (int i = 0; i < numObjects; ++i)
{
auto objectRef = mgr.getObjectByIndex(i);
if (!objectRef->isLocked()) // if object is not locked, it is finished updating
{
std::cout << "Object " << objectRef->getId() << " completed. Value = " << objectRef->getValue() << std::endl;
mgr.removeObjectByIndex(i);
numCompleted++;
}
}
}
system("pause");
}
Looks like you've got a thread that is trying to join itself.
While I was trying to understand your solution I was simplifying it a lot. And I come to point that you use std::thread::join() method in a wrong way.
std::thread provide capabilities to wait for it completion (non-spin wait) -- In your example you wait for thread completion in infinite loop (snip wait) that will consume CPU time heavily.
You should call std::thread::join() from other thread to wait for thread completion. Mutex in Object in your example is not necessary. Moreover, you missed one mutex to synchronize access to std::cout, which is not thread-safe. I hope the example below will help.
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <thread>
#include <mutex>
#include <chrono>
#include <cassert>
// cout is not thread-safe
std::recursive_mutex cout_mutex;
class Object {
public:
Object(int sleepTime, unsigned int id)
: _sleepTime(sleepTime), _id(id), _value(0) {}
void runUpdate() {
if (!_thread.joinable())
_thread = std::thread(&Object::_update, this);
}
void waitForResult() {
_thread.join();
}
unsigned int getId() const { return _id; }
unsigned int getValue() const { return _value; }
private:
void _update() {
{
{
std::lock_guard<std::recursive_mutex> lock(cout_mutex);
std::cout << "thread " << _id << " sleeping for " << _sleepTime << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(_sleepTime));
_value = _id * 10;
}
std::lock_guard<std::recursive_mutex> lock(cout_mutex);
std::cout << "Object " << getId() << " completed. Value = " << getValue() << std::endl;
}
unsigned int _sleepTime;
unsigned int _id;
unsigned int _value;
std::thread _thread;
};
class ObjectManager : public std::vector<std::shared_ptr<Object>> {
public:
void runUpdate() {
for (auto it = this->begin(); it != this->end(); ++it)
(*it)->runUpdate();
}
void waitForAll() {
auto it = this->begin();
while (it != this->end()) {
(*it)->waitForResult();
it = this->erase(it);
}
}
};
int main(int argc, char* argv[]) {
enum {
TEST_OBJECTS_NUM = 2,
};
srand(static_cast<unsigned int>(time(nullptr)));
ObjectManager mgr;
// Generate sleep time for each object
std::vector<int> objectSleepTimes;
objectSleepTimes.reserve(TEST_OBJECTS_NUM);
for (int i = 0; i < TEST_OBJECTS_NUM; ++i)
objectSleepTimes.push_back(rand() * 9 / RAND_MAX + 1); // 1..10 seconds
// Create some objects
for (int i = 0; i < TEST_OBJECTS_NUM; ++i)
mgr.push_back(std::make_shared<Object>(objectSleepTimes[i], i));
assert(mgr.size() == TEST_OBJECTS_NUM);
// Print expected object completion order
// Sort from smallest to largest
std::sort(objectSleepTimes.begin(), objectSleepTimes.end());
for (size_t i = 0; i < mgr.size(); ++i)
std::cout << objectSleepTimes[i] << ", ";
std::cout << std::endl;
// Update objects
mgr.runUpdate();
mgr.waitForAll();
//system("pause"); // use Ctrl+F5 to run the app instead. That's more reliable in case of sudden app exit.
}
About is it a reasonable thing to do...
A better approach is to create an object update queue. Objects that need to be updated are added to this queue, which can be fulfilled by a group of threads instead of one thread per object.
The benefits are:
No 1-to-1 correspondence between thread and objects. Creating a thread is a heavy operation, probably more expensive than most update code for a single object.
Supports thousands of objects: with your solution you would need to create thousands of threads, which you will find exceeds your OS capacity.
Can support additional features like declaring dependencies between objects or updating a group of related objects as one operation.
First, let me introduce you to my problem.
My code looks like this:
#include <iostream>
#include <thread>
#include <condition_variable>
std::mutex mtx;
std::mutex cvMtx;
std::mutex mtx2;
bool ready{false};
std::condition_variable cv;
int threadsFinishedCurrentLevel{0};
void tfunc() {
for(int i = 0; i < 5; i++) {
//do something
for (int j = 0; j < 10000; j++) {
std::cout << j << std::endl;
}
//this is i-th level
mtx2.lock();
threadsFinishedCurrentLevel++;
if (threadsFinishedCurrentLevel == 2) {
//this is last thread in current level
threadsFinishedCurrentLevel = 0;
cvMtx.unlock();
}
mtx2.unlock();
{
//wait for notify
unique_lock<mutex> lck(mtx);
while (!ready) cv_.wait(lck);
}
}
}
int main() {
cvMtx.lock(); //init
std::thread t1(tfunc);
std::thread t2(tfunc);
for (int i = 0; i < 5; i++) {
cvMtx.lock();
{
unique_lock<mutex> lck(mtx);
ready = true;
cv.notify_all();
}
}
t1.join();
t2.join();
return 0;
}
I have 2 threads. My computation consists of levels(for this example, lets say we have 5 levels). On the same level, computation can be divided to threads. Each thread then calculates part of a problem. When i want to step to the next(higher) level, lower level must be first done. So my idea is something like this. When last thread on the current level is done, it unlocks main thread, so it can notify all of the threads to continue to next level. But this notify has to be called more then once. Because there are plenty of these levels. Can this condition_variable be restarted or something? Or do I need for each level one condition_variable? So for example, when i have 1000 levels, i need to allocate dynamically 1000x condition_variable?
Is it just me or you are trying to block the main thread with a mutex (which is your way of trying to notify it when all threads are done?), I mean that's not the task of a mutex. That's where the condition variable should be used.
// New condition_variable, to nofity main thread when child is done with level
std::condition_variable cv2;
// When a child is done, it will update this counter
int counter = 0; // This is already protected by cvMtx, otherwise it could be atomic.
// This is to sync cout
std::mutex cout_mutex;
void tfunc()
{
for (int i = 0; i < 5; i++)
{
{
std::lock_guard<std::mutex> l(cout_mutex);
std::cout << "Level " << i + 1 << " " << std::this_thread::get_id() << std::endl;
}
{
std::lock_guard<std::mutex> l(cvMtx);
counter++; // update counter &
}
cv2.notify_all(); // notify main thread we are done.
{
//wait for notify
unique_lock<mutex> lck(mtx);
cv.wait(lck);
// Note that I've removed the "ready" flag here
// That's because u would need multiple ready flags to make that work
}
}
}
int main()
{
std::thread t1(tfunc);
std::thread t2(tfunc);
for (int i = 0; i < 5; i++)
{
{
unique_lock<mutex> lck(cvMtx);
// Wait takes a predicate which u can take advantage of
cv2.wait(lck, [] { return (counter == 2); });
counter = 0;
// This thread will get notified multiple times
// But it only will wake up when counter matches 2
// Which equals to how many threads we've created.
}
// Sleeping a bit to know the code is working
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// Wake up all threds and continue to next level.
unique_lock<mutex> lck(mtx);
cv.notify_all();
}
t1.join();
t2.join();
return 0;
}
The synchronization can be done with a single counter, threads increment the counter under lock and check for the counter to reach a multiple of the number of concurrent threads. This greatly simplifies the logic. I've made this change and also grouped the shared variables into a class, and provided member functions to access them. To avoid false sharing I've ensured that variables that are read-only are separate from those that are read-write by the threads, and also separated read-write variables by usage. The use of global variables is discouraged, see C++ Core Guidelines for this and other good advice.
The simplified code follows, you can see it live in ideone. Note: it looks like there isn't true concurrency in ideone, you'll have to run this on a multi-core environment to actually test hardware concurrency.
//http://stackoverflow.com/questions/35318942/stdcondition-variable-calling-notify-all-more-than-once
#include <iostream>
#include <functional>
#include <thread>
#include <mutex>
#include <vector>
#include <condition_variable>
static constexpr size_t CACHE_LINE_SIZE = 64;
static constexpr size_t NTHREADS = 2;
static constexpr size_t NLEVELS = 5;
static constexpr size_t NITERATIONS = 100;
class Synchronize
{
alignas(CACHE_LINE_SIZE) // read/write while threads are busy working
std::mutex mtx_std_cout;
alignas(CACHE_LINE_SIZE) // read/write while threads are synchronizing at level
std::mutex cvMtx;
std::condition_variable cv;
size_t threadsFinished{0};
alignas(CACHE_LINE_SIZE) // read-only parameters
const size_t n_threads;
const size_t n_levels;
public: // class Synchronize owns unique resources:
// - must be explicitly constructed
// - disallow default ctor,
// - disallow copy/move ctor and
// - disallow copy/move assignment
Synchronize( Synchronize const& ) = delete;
Synchronize & operator=( Synchronize const& ) = delete;
explicit Synchronize( size_t nthreads, size_t nlevels )
: n_threads{nthreads}, n_levels{nlevels}
{}
size_t nlevels() const { return n_levels; }
std::mutex & std_cout_mutex() { return mtx_std_cout; }
void level_done_wait_all( size_t level )
{
std::unique_lock<std::mutex> lk(cvMtx);
threadsFinished++;
cv.wait(lk, [&]{return threadsFinished >= n_threads * (level+1);});
cv.notify_all();
}
};
void tfunc( Synchronize & sync )
{
for(size_t i = 0; i < sync.nlevels(); i++)
{
//do something
for (size_t j = 0; j < NITERATIONS; j++) {
std::unique_lock<std::mutex> lck(sync.std_cout_mutex());
if (j == 0) std::cout << '\n';
std::cout << ' ' << i << ',' << j;
}
sync.level_done_wait_all(i);
}
}
int main() {
Synchronize sync{ NTHREADS, NLEVELS };
std::vector<std::thread*> threads(NTHREADS,nullptr);
for(auto&t:threads) t = new std::thread(tfunc,std::ref(sync));
for(auto t:threads) {
t->join();
delete t;
}
std::cout << std::endl;
return 0;
}