Search code examples
c++multithreadingc++11stdthread

Telling an std::thread to kill/stop itself when a condition is met


Say I have a worker thread tWorker, which is initialized when Boss is constructed and tells it to do work(), until bRetired is true. An std::mutex, mtx, locks some data (vFiles) so that tWorker owns it when he's working on it.

How do I make tWorker "commit suicide" once bRetired becomes true? How would the mutex be destroyed when the thread stops execution?

I've read that std::thread objects cannot be interrupted in any way. Does letting the thread do nothing (or calling std::this_thread::yield()) provide the same effect as killing the thread?

class Boss {
private:
    std::thread tWorker;
    std::mutex mtx;
    bool bRetired;
    std::vector< std::string > vFiles;

    void work() {
        while ( bRetired == false ) {
            // Do your job!
            mtx.lock();
            // ... Do something about vFiles ...
            mtx.unlock();
        }

        // tWorker has retired, commit suicide
        // ** How? **

        // Does this suffice if I want to "kill" the thread?
        std::this_thread::yield(); 
    }

public:
    Boss() {
        bRetired = false;
        tWorker = std::thread( &Boss::work, this );

        // Have worker do its job independently
        // **Bonus Question** : Should this be tWorker.join() or tWorker.detach()?
        tWorker.detach();
    }

    retire() {
        bRetired = true;
    }
}

Notes

  • The worker thread cannot be started again once it is retired.
  • The worker thread works on the background without interrupting the main thread's execution.

Solution

  • The call to std::thread::yield() is unrequired and does not kill the calling thread:

    Provides a hint to the implementation to reschedule the execution of threads, allowing other threads to run.

    Just exit the function to exit the thread.

    Note that the use of bRetired is incorrect as two threads can be accessing the same memory location and one of those threads is modifying it: this is undefined behaviour. Also, the change made in the function retire(), a different thread, will not be seen by the thread executing run(): use atomic<bool> for atomicity and visibility.

    If join() was used within the constructor the constructor would not return until the thread exited, which would never happen as it would be impossible to call retire() because the object would not be available (as the constructor would not have returned). If it is required to synchronize with the exiting of the thread then do not detach() but join() in the retire() function:

    void retire() {
        bRetired = true;
        tWorker.join();
    }
    

    Use RAII for acquiring mutexes (std::lock_guard for example) to ensure it always released. The mutex will be destroyed when it goes out of scope, in this case when its containing class is destructed.