如何放弃或取消 std::thread

Posted

技术标签:

【中文标题】如何放弃或取消 std::thread【英文标题】:How to abandon or cancel std::thread 【发布时间】:2019-07-15 16:46:16 【问题描述】:

我正在使用 std::threads 来实现一个生产者,它会进行一些长时间的计算,但使用的代码没有提供中止方法。但是我需要调用线程在请求​​中止后立即继续。 所以我试图通过调用 detach() 来放弃线程。思路是线程完成计算,但结果被丢弃:

#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();

这种方法似乎有效,但对我来说它闻起来很臭。 我不明白为什么线程不会导致访问冲突,因为它会在对象被销毁后尝试访问它。

有没有办法通知线程它已经被分离并且必须退出而不访问任何类成员。

【问题讨论】:

为什么你认为它在对象被销毁后正在访问某些东西? 在对象被删除后访问对象是UB,可能的表现之一是程序看起来像预期的那样工作。 @NathanOliver 可以。 C 在退出作用域后在主线程中被破坏,没有什么可以保证生产者会在此之前退出。 强行杀死(取消)线程是一个真的坏主意。线程都共享创建进程的相同地址空间,因此如果一个线程死了,您将不知道地址空间的状态是什么。要么通过join()ing 等待线程终止,要么向它们发送一个信号,告诉它们它们应该通过一些atomicmutex 受保护的变量退出,它们已被写入定期检查。还阅读“条件变量”。简而言之:不要取消/杀死线程。请他们退出并写信让他们这样做。然后join他们知道他们已经这样做了。 这段代码闻起来像 std::futurestd::promisestd::packaged_task 的自己实现。 【参考方案1】:
t1.swap(std::thread(&C::producer, this, 1));

上面的格式不正确,因为声明了std::thread::swap

void swap( thread& other ) noexcept;

std::thread(&amp;C::producer, this, 1) 是一个临时的,因此是一个右值。它不能绑定到swap 的非常量左值引用。

也许你想改写t1 = std::thread(&amp;C::producer, this, 1);


我不明白为什么线程不会导致访问冲突

在对象的生命周期之外访问对象的行为是未定义的。不保证会导致访问冲突。

这样好吗

没有。

或者有没有更好的方法来实现这样的东西?

理想的解决方案视情况而定。

一个简单的解决方案是使用有状态的可调用对象来创建线程,并存储一个指向共享状态的共享指针。谁活得久,谁就活下去。如果经常访问状态,这可能会对性能产生影响。

更简单的解决方案是使用静态存储来存储共享状态。这没有共享指针可能存在的潜在性能问题,但全局状态存在其他问题。

【讨论】:

@SergeyA 他可能正在使用允许启用或忽略警告的 MSVC 扩展进行编译,但这不是一个好主意。 @SergeyA 至少在 GCC 中一个编译错误。也就是说,允许编译器不会使格式错误的程序编译失败。他们不需要失败。 @SergeyA 公平地说,MSVC 并不孤单,GCC 在 GNU-isms 中占有相当大的份额,这种特殊情况是从 VS2017 开始创建的新项目(如果不是 2015 年)的编译错误(其中引入了 permissive- 标志)。 共享指针完成这项工作:接受答案。我也会发布一个解决方案。【参考方案2】:

在您的 C 类被销毁后,您的线程清楚地访问了数据。这是未定义的行为,因此不要依赖于此

如果我理解正确,您希望能够在退出程序之前终止线程。这本质上是不安全的,因为当您终止线程时您不知道线程正在访问什么内存,并且您的程序很可能会处于错误状态。

终止线程的唯一正确方法是关闭线程本身。您已经在中止功能中执行此操作。这会将aborted 变量设置为true,这反过来会破坏你的线程循环并退出你的线程函数。

但与其分离你正在做的事情,不如加入线程。这将使您的主线程等到您的工作线程在您恢复之前终止。在此之后删除 C 是安全的。

【讨论】:

假设线程内的计算需要一些时间并且不能取消(第三方库)。所以使用join()会阻塞调用线程太长时间。 那么你必须要么终止整个过程,让操作系统为你做清理工作,要么接受打击并等待。你不能做的就是终止线程。 您可以在另一个进程中启动第三方库,并通过某种 IPC 机制与之通信。【参考方案3】:

正如接受的答案中所建议的,shared_ptr 可以提供所需的行为。 在中止情况下,shared_ptr 的寿命与最后一个被放弃的线程一样长。

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

class C

public:
    ~C()
    
        std::cout << "Dtor\n";
    


    void abort()
    
        std::lock_guard<std::mutex> lk(mx);
        aborted = true;
    

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

    static void staticProducer(std::shared_ptr<C> arg, int id)
    
        arg->producer(id);
    

    void producer(int id)
    
        int i = 0;
        while (i<300000) 
            std::lock_guard<std::mutex> lk(mx);
            if (aborted) break;
            q.push(id * 10 + i);
            i++;
            std::cout << "Push\n";
        
    
;

int main()

    
        std::shared_ptr<C> c = std::make_shared<C>();
        std::thread t1 = std::thread(&C::staticProducer, c, 1);

        std::this_thread::sleep_for(std::chrono::milliseconds(2000));

        // scenario 1: abort
        c->abort();
        t1.detach();

        // scenario 2: wait for completion and get result
        // t1.join();
        // std::cout << c->q.size() << "\n";
    

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

【讨论】:

以上是关于如何放弃或取消 std::thread的主要内容,如果未能解决你的问题,请参考以下文章

我为什么放弃 Google Offer?

使用 native_handle() + pthread_cancel() 取消 std::thread

如何放弃改变?

cs3k.com 我为什么放弃 Google Offer?

在给定时间段后关闭或放弃 MFC 对话框

pandas操作mysql从放弃到入门