Search code examples
c++multithreadingstdthread

How to abandon or cancel std::thread


I am using std::threads to implement a producer, which does some long time computation, but the code used, does not provide an abort method. But I need the calling thread to move on immediately after an abort was requested. So I tried to just abandon the thread by calling detach(). The idea is that the thread completes the computation, but the result is discarded:

#include <thread>
#include <iostream>
#include <queue>
#include <mutex>

class C
{
public:
    void spawn()
    {
        t1.swap(std::thread(&C::producer, this, 1));
    }


    void abort()
    {
        std::lock_guard<std::mutex> lk(mx);
        aborted = true;
        t1.detach();
    }

    bool tryGetResult(int &result)
    {
        std::lock_guard<std::mutex> lk(mx);
        if (!q.empty()) {
            result = q.front();
            q.pop();
            return true;
        }
        else
            return false;
    }

private:
    std::thread t1;
    std::mutex mx;
    std::queue<int> q;
    bool aborted = false;

    void producer(int id)
    {
        int i = 0;
        while (true) {
            std::lock_guard<std::mutex> lk(mx);
            if (aborted) break;
            q.push(id * 10 + i);
            i++;
        }
    }
};

int main()
{
    {
        C c;
        c.spawn();
        std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        c.abort();
    }

    std::cout << "Done...";
    std::cin.get();
}

This approach appears to work, but for me it smells. I don't understand why the thread does not cause an access violation, because it tries to access the object after it has been destroyed.

Is there a way to inform the thread that it has been detached and must exit without accessing any class member.


Solution

  • t1.swap(std::thread(&C::producer, this, 1));
    

    The above is ill-formed, because std::thread::swap is declared:

    void swap( thread& other ) noexcept;
    

    std::thread(&C::producer, this, 1) is a temporary, and thus an rvalue. It cannot be bound to the non-const lvalue reference of swap.

    Perhaps you inteded to write t1 = std::thread(&C::producer, this, 1); instead.


    I don't understand why the thread does not cause an access violation

    The behaviour of accessing an object outside of its lifetime is undefined. It is not guaranteed to cause an access violation.

    Is this OK

    No.

    or is there a better way to implement something like this?

    The ideal solution depends on the case.

    A simple solution is to use a stateful callable to create the thread, and store a shared pointer to the shared state. It will be kept alive by whoever lives longer. This can have performance implications if the state is accessed often.

    Even simpler solution is to use static storage for the shared state. This doesn't have the potential performance issues that shared pointer may have, but there are other problems with global state.