3

I need a C++ equivalent of pthread_exit in order to make a function that when called cause the calling thread to exit.

In C, using pthreads.h I simply called pthread_exit. I'm new to C++ and need to use its <thread> for which I cannot find a similar function.

I'm using C++17, and the code has to compile on Linux (and possibly on MacOs).

ninazzo
  • 547
  • 1
  • 6
  • 17
  • 3
    sounds like an anti-pattern – Richard Hodges Apr 17 '20 at 21:16
  • To stop my threadpool I'll submit tasks containing a function that terminate the thread, and then I'll call join from the destructor of the threadpool. I already know that there are better solutions. – ninazzo Apr 17 '20 at 21:21
  • Are you trying to cause the current thread to exit, or do you want a function that is *given* a `thread` to cause it to exit? – Nicol Bolas Apr 17 '20 at 21:22
  • I want the thread calling that function to exit. So when the worker thread will pop from the queue of tasks this terminating task, it will exit. – ninazzo Apr 17 '20 at 21:28
  • Does this answer your question? [How do I terminate a thread in C++11?](https://stackoverflow.com/questions/12207684/how-do-i-terminate-a-thread-in-c11) – P.P Apr 17 '20 at 21:53
  • @ninazzo maybe you can provide a bit more information on what your thread is doing - there maybe a better way to achieve what you want. Is it running an uninterruptible computation? or is it looping forever, etc - E.g. if your thread is running some sort of loop, you might just have an exit flag or such. – code_fodder Apr 17 '20 at 22:03
  • You can still call pthread_exit by obtaining the native handle from c++11, though it's not nice, but if all your platforms support posix threads, shouldn't be a problem. – dhanushka Apr 18 '20 at 00:26
  • @dhanushka, Where does documentation for the C++ standard library say that `std::thread` _must_ be implemented as a layer on top of Posix threads? And, where does it say that bypassing the library and killing a thread by a direct call to `pthread_exit(...)` will always leave the library in a safe, well-defined state? – Solomon Slow Apr 18 '20 at 13:52
  • TLDR: Throw an exception. Answers below say that the only safe way to terminate a thread is for it's top-level function to return. Throwing an exception is how you can get from the point where the need to terminate was discovered, back to the top-level. – Solomon Slow Apr 18 '20 at 13:57
  • @SolomonSlow The OP's platforms are all POSIX conformant, so just assumed pthread_exit may be accessible by std::thread::native_handle. I don't know, if letting users access the native handle is unsafe, why would the std lib designers do it? – dhanushka Apr 18 '20 at 14:46
  • @dhanushka, Re, "Why would the std lib designers do it?" How could they _stop_ you from linking in any library you want? calling any function (including any OS function) that you want to call? C++ is not one of those toy learn-to-program environments that are designed to make it hard for newbies to make mistakes. It gives programmers [plenty of rope with which to shoot themselves in the foot](https://www.amazon.com/Enough-Rope-Shoot-Yourself-Foot/dp/0070296898/). – Solomon Slow Apr 18 '20 at 14:58

2 Answers2

3

There's no direct way to do this - the normal way to terminate a thread is to return from the top-level function that is called when the thread starts up - but you can achieve the same effect by throwing an exception at the point where you wish to terminate the thread and catching it in the thread's top-level function. This has the advantage that the thread's stack is correctly unwound and any relevant destructors get called.

For example:

#include <iostream>
#include <thread>
#include <exception>

class thread_exit_exception : public std::exception {};

void thread_subfunc ()
{
    std::cout << "Entering thread_subfunc\n";
    thread_exit_exception e;
    throw e;
    std::cout << "Leaving thread_subfunc (never executed)\n";
}

void thread_func ()
{
    std::cout << "Entering thread_func\n";
    try
    {
        thread_subfunc ();
    }
    catch (const thread_exit_exception&)
    {
    }
    std::cout << "Leaving thread_func\n";
}

int main()
{
    std::cout << "Entering main\n";
    std::thread t = std::thread (thread_func);
    t.join ();
    std::cout << "Leaving main\n";
}

Output:

Entering main
Entering thread_func
Entering thread_subfunc
Leaving thread_func
Leaving main

Live demo

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
1

C++ is more reliant on its call stack than C is. C++ programs often use RAII, which means that resources are bound to objects that frequently live on the stack. Users of such objects expect those objects to be destroyed properly. If a function creates a stack object, it expects that at some point in the future, control will return to that function and the stack object will be destroyed.

As such, there is no mechanism for a thread with some stack depth to simply go away. A std::thread only ends when the end of the function passed to the thread constructor is reached (an exception emitted from a thread function invokes std::terminate).

Given this, it is best to restructure your code so that you never need to cause a thread function to exit from some arbitrary place in the call graph. Make it so that the only points where you would want the current thread to exit are places within the thread's main function.

For example, the typical way a thread pool works is that each thread's main function goes to sleep, waiting on some form of task to be dropped off for that thread. When a task is available, it executes that task. Upon the task's completion, it checks for a new task, and if none is available, it goes back to sleep until a task is ready.

In such a thread pool, no thread ever stops. Individual tasks stop, but the actual std::thread is eternal (or at least, lives as long as the pool).

If a task needs to terminate, then such termination essentially represents a failure to perform the task. In C++, that's spelled "throwing an exception". The main thread would put all task invocations in a try block, with a catch block for the specific exception type. It can then report to whomever that a task failed, then go check for a new task.

And this makes sure that the task's call stack is cleaned up.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982