How to cleanly exit a threaded C++ program?

17,463

Solution 1

The usual way is to set an atomic flag (like std::atomic<bool>) which is checked by all threads (including the main thread). If set, then the sub-threads exit, and the main thread starts to join the sub-threads. Then you can exit cleanly.

If you use std::thread for the threads, that's a possible reason for the crashes you have. You must join the thread before the std::thread object is destructed.

Solution 2

Others have mentioned having the signal-handler set a std::atomic<bool> and having all the other threads periodically check that value to know when to exit.

That approach works well as long as all of your other threads are periodically waking up anyway, at a reasonable frequency.

It's not entirely satisfactory if one or more of your threads is purely event-driven, however -- in an event-driven program, threads are only supposed to wake up when there is some work for them to do, which means that they might well be asleep for days or weeks at a time. If they are forced to wake up every (so many) milliseconds simply to poll an atomic-boolean-flag, that makes an otherwise extremely CPU-efficient program much less CPU-efficient, since now every thread is waking up at short regular intervals, 24/7/365. This can be particularly problematic if you are trying to conserve battery life, as it can prevent the CPU from going into power-saving mode.

An alternative approach that avoids polling would be this one:

  1. On startup, have your main thread create an fd-pipe or socket-pair (by calling pipe() or socketpair())
  2. Have your main thread (or possibly some other responsible thread) include the receiving-socket in its read-ready select() fd_set (or take a similar action for poll() or whatever wait-for-IO function that thread blocks in)
  3. When the signal-handler is executed, have it write a byte (any byte, doesn't matter what) into the sending-socket.
  4. That will cause the main thread's select() call to immediately return, with FD_ISSET(receivingSocket) indicating true because of the received byte
  5. At that point, your main thread knows it is time for the process to exit, so it can start directing all of its child threads to start shutting down (via whatever mechanism is convenient; atomic booleans or pipes or something else)
  6. After telling all the child threads to start shutting down, the main thread should then call join() on each child thread, so that it can be guaranteed that all of the child threads are actually gone before main() returns. (This is necessary because otherwise there is a risk of a race condition -- e.g. the post-main() cleanup code might occasionally free a resource while a still-executing child thread was still using it, leading to a crash)

Solution 3

The first thing you must accept is that threading is hard.

A "program using threading" is about as generic as a "program using memory", and your question is similar to "how do I not corrupt memory in a program using memory?"

The way you handle threading problem is to restrict how you use threads and the behavior of the threads.

If your threading system is a bunch of small operations composed into a data flow network, with an implicit guarantee that if an operation is too big it is broken down into smaller operations and/or does checkpoints with the system, then shutting down looks very different than if you have a thread that loads an external DLL that then runs it for somewhere from 1 second to 10 hours to infinite length.

Like most things in C++, solving your problem is going to be about ownership, control and (at a last resort) hacks.

Like data in C++, every thread should be owned. The owner of a thread should have significant control over that thread, and be able to tell it that the application is shutting down. The shut down mechanism should be robust and tested, and ideally connected to other mechanisms (like early-abort of speculative tasks).

The fact you are calling exit(0) is a bad sign. It implies your main thread of execution doesn't have a clean shutdown path. Start there; the interrupt handler should signal the main thread that shutdown should begin, and then your main thread should shut down gracefully. All stack frames should unwind, data should be cleaned up, etc.

Then the same kind of logic that permits that clean and fast shutdown should also be applied to your threaded off code.

Anyone telling you it is as simple as a condition variable/atomic boolean and polling is selling you a bill of goods. That will only work in simple cases if you are lucky, and determining if it works reliably is going to be quite hard.

Solution 4

Additional to Some programmer dude answer and related to discussion in the comment section, you need to make the flag that controls termination of your threads as atomic type.

Consider following case :

bool done = false;
void pending_thread()
{
    while(!done)
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

Here worker thread can be your main thread also and done is terminating flag of your thread, but pending thread need to do something with given data by working thread, before exiting.

this example has race condition and undefined behaviour along with it, and it's really hard to find what is the actual problem int the real world.

Now the corrected version using std::automic :

std::atomic<bool> done(false);
void pending_thread()
{
    while(!done.load())
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

You can exit thread without being concern of race condition or UB.

Share:
17,463
rjlion795
Author by

rjlion795

Updated on June 02, 2022

Comments

  • rjlion795
    rjlion795 almost 2 years

    I am creating multiple threads in my program. On pressing Ctrl-C, a signal handler is called. Inside a signal handler, I have put exit(0) at last. The thing is that sometimes the program terminates safely but the other times, I get runtime error stating

    abort() has been called
    

    So what would be the possible solution to avoid the error?