Since C++11 we have std::thread
, so since then we could say that C++ is aware of threads. However, those may not be pthreads (it is under Linux but it is an implementation detail) and you specifically mentioned that you used pthreads...
One thing I wanted to add from kris's answer is the fact that it's actually a little more complicated to handle threads than one first thinks it is. In most cases, people create one class for RAII (Resource Acquisition Is Initialization.)
Here is an example with a block of memory, to keep it simple (but consider using an std::vector
or std::array
for buffer management in C++):
class buffer
{
public:
buffer()
: m_buffer(new char[1024])
{
}
~buffer()
{
delete [] m_buffer;
}
char * data()
{
return m_buffer;
}
private:
char * m_buffer;
};
What this class does is manage the lifetime of the m_buffer
pointer. On construction it allocates a buffer and on destruction it releases it. Up to here, nothing new.
With threads, though, you have a potential problem because the class the thread is running in must remain in good standing before it gets destroyed and as not so many C++ programmers know, once you reach a destructor, it's too late to do certain things... specifically, calling any virtual functions.
So a basic class as follow is actually incorrect:
// you could also hide this function inside the class, see "class thread" below
class runner;
void start_func(void * data)
{
((runner *) data)->run();
}
class runner
{
public:
runner()
{
// ...setup attr...
m_thread = pthread_create(&m_thread, &attr, &start_func, this);
}
virtual ~runner()
{
stop(); // <-- virtual function, we may be calling the wrong one!
pthread_join(m_thread);
}
virtual void run() = 0;
virtual void stop()
{
m_stop = true;
}
private:
pthread_t m_thread;
bool m_stop = false;
};
This is wrong because the stop()
function may require calls to some virtual function defined in your derived version of the class. Also your run()
function is very likely to make use of virtual functions before it exists. Some of which may be pure virtual functions. Calling those within the ~runner()
function was called is going to end up with an std::terminate()
.
The solution to this problem is to have two classes. A runner with that run()
pure virtual function, and a thread. The thread class is responsible for deleting the runner after the pthread_join()
.
The runner is redefined to not include anything about the pthread:
class runner
{
public:
virtual void run() = 0;
virtual void stop()
{
m_stop = true;
}
private:
bool m_stop = false;
};
The thread class handles the stop()
and that can happen in its destructor:
class thread
{
public:
thread(runner *r)
: m_runner(r)
{
// ...setup attr...
m_thread = pthread_create(&m_thread, &attr, &start_func, this);
}
~thread()
{
stop();
}
void stop()
{
// TODO: make sure that a second call works...
m_runner->stop();
pthread_join(m_thread);
}
private:
static void start_func(void * data)
{
((thread *) data)->start();
}
void start()
{
m_runner->run();
}
runner * m_runner;
pthread_t m_thread;
};
Now when you want to use your runner, you overload it and implement a run()
function:
class worker
: runner
{
public:
virtual void run()
{
...do heavy work here...
}
};
Finally, you can use such safely when you make sure that the thread gets deleted first. This means defined second (which the classes enforces since you need to pass a runner to the thread!)
int main()
{
worker w;
thread t(&w);
...do other things...
return 0;
}
Now in this case C++ takes care of the clean up, but only because I used return
and not exit()
.
However, the solution to your problem are exceptions. My main()
is also exception safe! The thread will cleanly be stopped before the std::terminate()
gets called (because I do not have a try/catch, it will terminate).
One way to do "an exit from anywhere" is to create an exception allowing you to do that. So the main()
would become something like this:
int main()
{
try
{
worker w;
thread t(&w);
...do other things...
}
catch(my_exception const & e)
{
exit(e.exit_code());
}
return 0;
}
I'm sure many people will comment about the fact that you should never use an exception to exit your software. The fact is that most of my software have such a try/catch so I can at least log the error that happened and it's just very similar to using an "exit exception".
WARNING: The std::thread
is different from my thread
class above. It accepts a function pointer to execute some code. It will call pthread_join()
(at least on g++ Linux) on destruction. However, it does not tell your thread code anything. If you need to listen to some signal to know that it has to quit, you're in charge. It's a quite different way of thinking, however, it is also safe to use (outside of that missing signal).
For a complete implementation, you may want to look at my snap_thread.cpp/h on Github in the Snap! C++ project. My implementation includes many more features, especially, it has a FIFO which you can use to safely pass work loads to your threads.
What about detaching threads?
I used that for a while too. The fact is that only a pthread_join()
is 100% safe. Detaching means that the thread is still running and your main process quitting is likely to crash the thread. Although in my end I would tell the thread to quit and would wait for the "finished" signal to be set, it would still crash once in a while. It could take like 3 months or so before I'd see an unexplained crash, but it would happen. Since I removed that and always use join, I do not see those unexplained crashes. Good proof that you do not want to use that special detach thread feature.