34

Is it possible to set a timeout for a call to std::thread::join()? I want to handle the case in which the thread is taking too long to run, or terminate the thread. I may be doing this for multiple threads (say, up to 30).

Preferably without boost, but I'd be interested in a boost solution if that's the best way.

ty.
  • 10,924
  • 9
  • 52
  • 71

6 Answers6

28

There is no timeout for std::thread::join(). However you can view std::thread::join() as merely a convenience function. Using condition_variables you can create very rich communication and cooperation between your threads, including timed waits. For example:

#include <chrono>
#include <thread>
#include <iostream>

int thread_count = 0;
bool time_to_quit = false;
std::mutex m;
std::condition_variable cv;

void f(int id)
{
    {
    std::lock_guard<std::mutex> _(m);
    ++thread_count;
    }
    while (true)
    {
        {
        std::lock_guard<std::mutex> _(m);
        std::cout << "thread " << id << " working\n";
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(250));
        std::lock_guard<std::mutex> _(m);
        if (time_to_quit)
            break;
    }
    std::lock_guard<std::mutex> _(m);
    std::cout << "thread ended\n";
    --thread_count;
    cv.notify_all();
}

int main()
{
    typedef std::chrono::steady_clock Clock;
    std::thread(f, 1).detach();
    std::thread(f, 2).detach();
    std::thread(f, 3).detach();
    std::thread(f, 4).detach();
    std::thread(f, 5).detach();
    auto t0 = Clock::now();
    auto t1 = t0 + std::chrono::seconds(5);
    std::unique_lock<std::mutex> lk(m);
    while (!time_to_quit && Clock::now() < t1)
        cv.wait_until(lk, t1);
    time_to_quit = true;
    std::cout << "main ending\n";
    while (thread_count > 0)
        cv.wait(lk);
    std::cout << "main ended\n";
}

In this example main launches several threads to do work, all of which occasionally check if it is time to quit under a mutex (this could also be an atomic). The main thread also monitors if it is time to quit (if the threads get all their work done). If main runs out of patience, he just declares it to be time to quit, then waits for all threads to perform any necessary clean up before exiting.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 1
    This isn't quite right, as `thread_count` needs to be atomic, doesn't it? – Mooing Duck May 28 '14 at 21:58
  • 2
    @MooingDuck: Not in this case because everywhere `thread_count` is read or written is under the locked state of `m`. So making `thread_count` atomic would be correct, but redundant and unnecessarily expensive. – Howard Hinnant May 29 '14 at 02:01
  • Actually, what caught my attention was `while(thread_count>0) cv.wait(lk);`. Isn't that one of those thingies that's allowed to cache `thread_count` and not work "how you'd expect"? – Mooing Duck May 29 '14 at 02:44
  • 1
    @MooingDuck: `thread_count` is a global. Near program end it is modified by each thread while locked by mutex `m`. In main it is read in the final while loop while locked by mutex `m`. If main waits on `cv`, main unlocks `m`, allowing a thread to lock `m`, change `thread_count`, notify main, and unlock `m`. Upon waking main will lock `m` and read `thread_count`. If the implementation doesn't load variables after acquiring the lock on a mutex, there is a bug in the implementation. – Howard Hinnant May 29 '14 at 14:56
  • Ok. I'm still not used to how the rules have changed with the locks and mutexes and stuff. I'm used to looking for a `volatile`. Good to know. – Mooing Duck May 29 '14 at 16:15
  • @MooingDuck The lock/unlock is what we call a barrier which means `thread_count` is not going to be cached (not between the barriers). The `cv.wait(lk)` unlocks, waits, then relocks, creating two barriers. – Alexis Wilke Apr 24 '18 at 18:34
  • @HowardHinnant This is not quite correct, albeit, I do not know of a good solution to prevent a crash, but detached threads can't be joined meaning that you may call `exit()` before all your threads are done. If that happens, you'll get a `std::terminate()` call or some less obvious explosion. This is because between the time `thread_count` goes to zero and the time the last thread quits, your main thread has a chance to try quitting itself. Detach threads just don't work right unless you don't have to quit imminently. – Alexis Wilke Apr 24 '18 at 18:37
  • @AlexisWilke Once all threads have returned from their initial function, it is safe to return from `main`. Even `thread_local` destructors are guaranteed to be sequenced before static storage destructors. There's a clarifying note to his effect in [basic.start.term]/p6. In my example above, all threads are guaranteed to return from `f` before `main` returns, and even before "main ended" is streamed to `cout`. – Howard Hinnant Apr 24 '18 at 21:54
  • @HowardHinnant, no. (1) if you don't detach and just call the destructor, it will `std::terminate()`. You must `join()` first. (2) if you `detach()`, the thread is not `joinable()`, so the destructor can be called at any time even while the thread is still running (which you do since `std::thread(f, 1).detach();` does not save the thread object in a variable.) So the thread can still be running and your `main()` can attempt an early exit. In many cases, it won't hurt, once in a while you get a crash. Depends on so many parameters, it's really difficult to generate a crash to prove my point... – Alexis Wilke Apr 24 '18 at 23:44
  • @AlexisWilke (1) I know the spec. I was lead author on it. :-) And I _do_ `detach()` so I'm not sure why we're discussing the case where one doesn't. (2) Yes, `~thread()` is called long before the thread of execution finishes. This is not a problem. `~thread()` has no effects in this code. (3) You mention an "early exit" from `main`. I presume you're talking about `main` throwing an exception? This would be uncaught in my code and would indeed result in `terminate()`. If no exception is thrown, my code effectively joins with all threads and _then_ outputs "main ended", and terminates normally. – Howard Hinnant Apr 25 '18 at 01:11
  • `while (!time_to_quit && Clock::now() < t1)` shouldn't: `while (Clock::now() < t1)` be enough? After all, only the main thread can set `time_to_quit` to true. – PJ_Finnegan Oct 17 '18 at 08:35
  • @PJ_Finnegan at the time I was probably thinking that there could be other (imaginary in this example) threads that might set `time_to_quit`, including even the target thread under some (not demonstrated) condition. – Howard Hinnant Oct 17 '18 at 14:14
  • Is it safe to use `cv.notify_all();` at the end of function `f`? What if the statement is never executed because i.e. an exception is thrown before? Isn't there a convenience class akin to `std::lock_guard` to guarantee the scope exit handling in all cases? – PJ_Finnegan Nov 07 '18 at 21:53
  • If `f` propagates out an exception on any thread, `std::terminate()` is called. If that is not the desired behavior, then `f` needs a try/catch/swallow at the top level. And in this case, having a scope exit handler to not only call `cv.notify_all()`, but also everything after the `while` loop in `f` would indeed be a very good design. I did not add such exception handling as I did not want to obfuscate the code that implemented / emulated a timed join. – Howard Hinnant Nov 07 '18 at 23:19
14

Yes, it is possible. The solution that has been suggested by Galik looks like this:

#include <thread>
#include <future>
...
// Launch the thread.
std::thread thread(ThreadFnc, ...);
...
// Terminate the thread.
auto future = std::async(std::launch::async, &std::thread::join, &thread);
if (future.wait_for(std::chrono::seconds(5)) 
    == std::future_status::timeout) {

  /* --- Do something, if thread has not terminated within 5 s. --- */

}

However, this essentially launches a third thread that performs the thread.join().

(Note: The destructor of future will block until thread has joined and the auxiliary thread has terminated.)


Maybe launching a thread just to bring another thread down is not what you want. There is another, portable solution without an auxiliary thread:

#include <thread>
#include <future>
...
// Launch the thread.
std::future<T_return>*  hThread 
  = new std::future<T_return>(std::async(std::launch::async, ThreadFnc, ...));
...
// Terminate the thread.
if (hThread->wait_for(std::chrono::seconds(5)) 
    == std::future_status::timeout) {

  /* --- Do something, if thread has not terminated within 5 s. --- */

} else
  delete hThread;

where T_return is the return type of your thread procedure. This scenario uses an std::future / std::async combination instead of an std::thread.

Note that hThread is a pointer. When you call the delete operator on it, it will invoke the destructor of *hThread and block until the thread has terminated.

I have tested both versions with gcc 4.9.3 on Cygwin.

ManuelAtWork
  • 2,198
  • 29
  • 34
  • 1
    Good approach, but I have to wait for several threads. (I use std::thread threads[nrOfThreads]) Is the first solution applicable in a for loop over threads? And what should I do if the timeout exceed? Thx – leon22 Mar 03 '16 at 12:47
  • The first half is a nice work-around, but the second half doesn't make sense plus it's bad style: Why don't you just use a future if you can put you're threads functionality in one function with future semantics? Why the plain pointer with new and delete? Obviously were talking about >= C++11, so this leaky approach is no good. And you don't need a pointer at all. – Superlokkus Mar 04 '16 at 13:51
  • @leon22: Handling of the timeout depends on the context of your application. If the process is about to terminate, I write a warning into the logfile and release the thread into oblivion. In other cases I might kill the thread. When the main thread of the process terminates, most operating systems will automatically kill all other threads within the process. – ManuelAtWork Mar 07 '16 at 07:43
8

Instead of using threads explicitly you can use std::async() to provide you with a std::future<> and you can do timed waits on the std::future:

http://en.cppreference.com/w/cpp/thread/future/wait_for

Galik
  • 47,303
  • 4
  • 80
  • 117
5

For Boost, timed_join() is now deprecated. Use try_join_for() instead:

myThread.try_join_for(boost::chrono::milliseconds(8000))
parsley72
  • 8,449
  • 8
  • 65
  • 98
Simon
  • 379
  • 6
  • 10
4

For Boost, see timed_join() for the version of join() with timeout.

Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
user1202136
  • 11,171
  • 4
  • 41
  • 62
0

The pthread_timedjoin_np() function performs a join-with-timeout. If the thread has not yet terminated, then the call blocks until a maximum time, specified in abstime. If the timeout expires before the thread terminates, the call returns an error.

int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime);

Compile and link with -pthread.