23

Suppose a program has several threads: t1, t2, etc. These are using pthreads. The t2 thread is sitting in a loop reading from a stream and accessing a variable with static storage duration.

Now suppose t1 calls exit(0).

(Further details: I have a program doing this on a Unix-based system, and is compiled with g++. The program appears to occasionally be crashing on shutdown with a stack trace that indicates the static variable is not valid.)

  • Does the thread get killed prior to the C++ object destruction?

  • Is C++ not aware of the threads, so these keep running until the C++ cleanup is complete?

  • Should the SIGTERM handler first shutdown or kill the threads before proceeding, or does this happen automatically?

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
mbells
  • 3,668
  • 3
  • 22
  • 21
  • 1
    that's why they invented the debuggers, and by the way, debuggers are software too ... – user2485710 Nov 07 '14 at 17:07
  • 2
    You probably should send your threads a signal before terminating, and join them all before exiting. – πάντα ῥεῖ Nov 07 '14 at 17:11
  • _"What happens? Does the thread get killed prior to the C++ object destruction? Or perhaps C++ is not aware of the threads, so these keep running until the C++ cleanup is complete?"_ How can we know, without seeing even a single bit of code? – πάντα ῥεῖ Nov 07 '14 at 17:13
  • 1
    Calling `exit` while other threads are still running will likely result in a crash. No C++ cleanup will be done. as @πάνταῥεῖ says you need to join all other threads before shutting down. – sjdowling Nov 07 '14 at 17:14
  • remember that `threads = shared memory`, it's not like as if a thread is an obscure and totally independent unit of work outside your main process/application, it's part of your application and it can access anything that your `main` function can unless you instruct it to do otherwise . – user2485710 Nov 07 '14 at 17:17
  • 1
    @user2485710 _`shared memory`_ I think the correct term is _'concurrently accessed memory'_. Shared memory is something different (used by means of sharing memory between independent processes). – πάντα ῥεῖ Nov 07 '14 at 17:36
  • If no cleanup is required or wanted, call std::abort(). – Martin James Nov 07 '14 at 18:11
  • @πάνταῥεῖ this is the first time I hear that, if you mix the concepts of threads and shared memory I don't know how you can possibly end up understanding or confusing this with interprocess communications/data . I still think that `shared memory` is appropriate, `concurrent` also implies something that is different from `parallel` for example, and it's also something that deviates from the idea of thread which doesn't imply that something is running concurrently with other units of work, threads on a single core machine do still exist and multiple threads running 1 after the other are a – user2485710 Nov 07 '14 at 20:06
  • plausible scenario . You are marking the concept of memory with an accent on `concurrency` and `access`, which is something that is not peculiar nor granted by the fact that you are dealing with threads. the concept of `thread` spans from software to hardware, from `green threads` to `"cpu" threads`, threads represent units of work at best, anything else like multithreading and concurrency is a plus and not a peculiar aspect of the concept of thread. – user2485710 Nov 07 '14 at 20:07
  • @user2485710 See [Wikipedia's Definition](http://en.wikipedia.org/wiki/Shared_memory). That said `thread != shared memory`. – πάντα ῥεῖ Nov 08 '14 at 10:46

3 Answers3

7

I'm answering the question in the title of your question, not the 3 bullet points, because I think the answers to the bullet point questions are irrelevant to answer the actual question.

Using exit when the program is in a random state - as you seem to suggest - is usually a rather brutal and undeterministic way to end a program even with a single thread. It doesn't even matter if the thread gets destroyed before object destruction or after, both ways result in nightmares. Remember, that each thread could be in a random state and accessing anything. And the stack objects of each thread will not be destroyed properly.

See the documentation of exit to see what it does and doesn't clean up.

The favored way I've seen to correctly shutdown a multithreaded program, is to make sure no thread is in a random state. Stop all the threads in some way or another, call a join on them where feasible, and from the last remaining thread call exit - or return if this happens in the main function.

An incorrect approach I've seen often is to correctly dispose of some objects, close some handles, and generally try to do a proper shutdown until everything goes wrong, and then call terminate. I advise against that.

Kevin
  • 16,549
  • 8
  • 60
  • 74
Peter
  • 5,608
  • 1
  • 24
  • 43
2

Let me give a try to answer your questions. Guys, correct me if i am going wrong.

Your program is crashing occasionally. This is the expected behavior. You have released all of the acquired resources. And your thread, which is alive is trying to access the resources, based on the information it has. If it is successful, it will run. If it isn't successful, it would crash.

Often the behavior would be sporadic. If the OS allocates the released resources to other processes, or if it uses the resources, then you would see your thread crashing. If not, your thread runs. This behavior is dependent on OS, Hardware, RAM, % of resources being utilized when the process died. Any over use of resources, etc, etc.

Does the thread get killed prior to the C++ object destruction? No. C++ doesn't have any inbuilt support for threads. P threads are just posix threads, which work with the underlying OS and provide you a functionality to create threads, if required. Technically, As threads are not a part of C++, the threads getting killed automatically is not possible. Correct me if i am wrong.

Is C++ is not aware of the threads, so these keep running until the C++ cleanup is complete? C++ is not aware of the threads. The same cannot be said for C++11

Should the SIGTERM handler first shutdown or kill the threads before proceeding, or does this happen automatically? Technically SIGTERM handler shouldn't kill the threads. Why do you want the OS handlers to kill the running threads? Every operating system works on the hardware to provide the functionality to the users. Not to kill any of the running processes. Well, Programmers must join the threads to the main, but there might be some cases where you want to let your threads run for some time. May be.

It is the software developer/vendor responsibility to write code which doesn't crash or end up in infinite loops, and to kill all running threads when required. OS cannot take the responsibility of these acts. This is the reason why, Windows/Apple certifies some softwares for their OS's. So, customers can buy that with peace of mind.

kris123456
  • 501
  • 1
  • 5
  • 15
  • 2
    C++11 is C++, perhaps better to say "The C++11 version of the C++ standard introduced multi-threading into the language" – amdn Nov 07 '14 at 18:29
  • Your understanding of `SIGTERM` (signals in general actually) is wrong. I do not that this is the right way of stopping a thread or even a process, but the default is that `SIGTERM` kills your process and all of its threads and it is the expected behavior. Making a difference between whether the main thread or a child receives the signals can be complicated. But it has been a Unix way of doing all sorts of things (see `SIGPIPE` and `SIGIO` as examples!) – Alexis Wilke Apr 25 '19 at 21:23
0

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.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156