I'm trying to set up a system where I can have an arbitrary number of parallel tasks, all of which wait for a go
signal, do their work, and then stop while a manager collects the results, prepares for the next batch of work, and so on.
The easy way I know to do this is to launch a packaged_task
for each job, for each iteration. However, I have got the impression that launching threads (and more so with the higher abstractions) have some considerable overhead, which I would want to avoid. My limited testing of those other options confirm that belief.
The best I've come up with so far is to have threads that are kept alive, and loop based on signals from condition_variable
entities.
std::mutex mx;
std::condition_variable global;
bool start = false;
bool ready = false;
std::map<int, std::atomic<bool>> status;
using namespace std;
void work(int n) {
while (true) {
{
unique_lock<mutex> lock{mx};
//std::cout<<"Job #"<<n<<" waiting for ready signal.\n";
while (!ready) { global.wait(lock); }
} {
unique_lock<mutex> lock{mx};
status[n] = false;
//std::cout<<"Job #"<<n<<" ready, waiting for start signal.\n";
while (!start) { global.wait(lock); }
}
this_thread::sleep_for(chrono::milliseconds(10)); // simulate work
status[n] = true;
}
}
int main(int argc, char ** argv) {
const auto num_jobs = (argc<=1? 1000:atoi(argv[1]));
vector<thread> jobs;
for (auto n=1;n<=num_jobs;n++) { jobs.emplace_back(work, n); }
while (true) {
cin.ignore(); {
unique_lock<mutex> lock{mx};
start = false;
ready = true;
status.clear();
global.notify_all();
} cin.ignore(); {
unique_lock<mutex> lock{mx};
ready = false;
start = true;
global.notify_all();
}
while (!all_of(status.begin(), status.end(), // Wait for all jobs
[](auto&& t){ return t.second.load(); }))
{ /*this_thread::sleep_for(chrono::seconds(1));*/ }
}
for (auto& job : jobs) { job.join(); }
}
I tried to compact the code, but tell me if it's too obfuscated. What the program does is to create some arbitrary number of workers, which wait for the launch signal from cin
and then perform the simulated work.
I had to create two stages for the task to possibly be in, in a ready-set-go mechanic where the thread lands in a waiting clause after completing its job. My first attempt was to use just one wait
for the task, but I can't figure out a a way to disable the single flag before some tasks are looping back around and then would start a second iteration immediately.
So basically what I'm asking: is this a good approach to the problem discussed in the first paragraph? If yes, can it be made better? Surely this is a well trodden issue for programmers trying to do parallel optimization, what is the conventional way to deal with my scenario?