I have the following complete, compilable example (also available as a gist) which spawns a small number of threads (echo pass
) and one other (echo block; sleep 1; echo unblock
). The second is so names because when I see block
in the output, the loop just spins its wheels even though the other 'pass' threads should have finished long before then. I would have expected it to output pass
however many times, with interspersed checking
(since the main thread is looping at this point) and then unblock
at the very end.
How can I get the expected behavior?
I realize this may very well be a symptom of using system
, but I've been using that to stand up a multithreading solution since I haven't received a working alternative yet. I note a couple of things that I should probably change, but I'm still researching those solutions. They may also be the source of the bug, though.
// -*- compile-command: "g++ -std=c++0x main.cpp;./a.out" -*-
#include <iostream>
#include <thread>
#include <map>
#include <unistd.h>
typedef char status_t;
/**
* I know I'm not supposed to be using system, but I don't know what
* else to use in this context. The program I'm starting is,
* theoretically, arbitrary.
*
* @param command a shell command to run, such as "pdflatex file.tex"
* @param running a sentinal boolean for whether this thread is still
* running
* @param exit_code_buf a buffer for the exit code
*
* @todo use std::{atomic,future,promise} appropriately...
*/
void run(std::string command, bool *running, status_t *exit_code_buf)
{
// TODO: add std::chrono::duration timeout
*running = true;
*exit_code_buf = system(command.c_str());
*running = false;
}
class ProcessGroup
{
std::map<std::pair<std::thread *, bool *>, status_t *> threads;
public:
void Add(std::string);
void Join();
};
/**
* Starts a process in a new thread and adds it to the map
*
* @param command a command to start
*
* @todo use std::{atomic,future,promise} appropriately...
*/
void
ProcessGroup::Add(std::string command)
{
status_t *status = new status_t(0);
bool *running = new bool(false);
std::thread *t = new std::thread(&run, command, running, status);
threads.insert(std::make_pair(std::make_pair(t, running), status));
}
/**
* Periodically checks all threads to see if they are still running.
* If one of them is, sleep for a small amount of time and check
* again.
*/
void
ProcessGroup::Join()
{
bool still_running;
do {
still_running = false;
for (auto it = threads.cbegin(); it != threads.cend(); ++it) {
still_running |= *it->first.second;
}
std::cout << "checking" << std::endl;
if (still_running) usleep (100000);
} while (still_running);
}
int main()
{
std::cout << "Program start." << std::endl;
ProcessGroup pg;
std::string
block("echo block; sleep 1; echo unblock"),
pass("echo pass");
pg.Add(block);
std::cout << "here" << std::endl;
for (int i = 0; i<10; i++) {
pg.Add(pass);
}
std::cout << "Joining threads..." << std::endl;
pg.Join();
std::cout << "Program end." << std::endl;
return 0;
}
compiling…
$ g++ -std=c++0x main.cpp; ./a.out
Program start.
here
Joining threads...
checking
block
checking
checking
checking
checking
checking
checking
checking
checking
checking
unblock
pass
pass
checking
pass
pass
pass
pass
pass
pass
pass
pass
checking
Program end.
Compilation finished at Fri Oct 3 23:49:45