Effective Modern C++ Item 37:确保std::thread…

2018-06-17 23:04:52来源:未知 阅读 ()

新老客户大回馈,云服务器低至5折

下面这段代码,如果调用func,按照C++的标准,程序会被终止(std::terminate)

void func()
{
    std::thread t([]
    {
        std::chrono::microseconds dua(1000);
        std::this_thread::sleep_for(dua);
    });
}

原因在于C++标准规定,std::thread的析构被调用时,std::thread必须是unjoinable的,否则std::terminate就会被调用。

std::thread有两种状态,joinable和unjoinable,unjoinable的std::thread包括:

  • 使用默认构造的std::thread。这种std::thread没有任何执行任务。
  • 被移动的std::thread。比如std::thread t2(std::move(t1)),这时t1的执行工作就转移给了t2,t1变成了unjoinable的状态。
  • 已经被join的std::thread。调用了join之后,std::thread就变成了unjoinable的状态。
  • 已经被detach的std::thread。detach会断开std::thread和执行任务之间的连接。

之前的func中创建的thread,在销毁时是属于joinable状态的(不是默认构造,没有被移动,没有join和detach,并且线程还在运行),所以按照C++的标准,程序会被强行终止。

为什么C++要采用这种暴力的方式?因为如果采用别的方式,都会导致相应的问题。

我们假设C++标准采用其他的方式,分别分析会有什么问题:

1. 在std::thread的析构里显式调用join。这种方式会导致潜在的性能问题,因为join是阻塞调用,那么意味着thread的析构就可能会阻塞,某些情况下并不希望thread join,而是满足一定的条件才join,比如下面这种代码:

void doSomething()
{
    std::thread t(doWork()); 
    if(someCondition())
    {
        t.join();
        getResult();
    }
}

代码的本意是在condition满足时才会join线程,不满足就直接返回。因为std::thread的析构里会显式join,那么即使condition不满足,在函数退出时也会join。如果doWork是耗时的步骤,那么不管condition满不满足,doSomething都会阻塞直到doWork完成。

2. 在std::thread的析构里显式调用detach。这种方式看上去不会有第一种方式的性能问题,其实更糟糕,可能会导致runtime error。比如下面这种代码:

void doSomething()
{
    std::vector<int> data;
    std::thread t([&data]
    {
        for (int i = 0; i <= 1000; ++i)
            data.push_back(i);
    });
}

当函数退出时,std::thread调用detach,那么线程的执行任务还在继续,函数栈的临时变量已被销毁,程序就会出现undefined行为,而且调试起来也很困难。detach本身就容易导致bug,所以这种方式是无法使用的。

由于上面的2个方式都有问题,所以C++采用了暴力终止程序的方式,实际上C++的这种做法强迫程序员必须保证std::thread销毁时有正确的行为,否则,你的程序就会被干掉。这是C++的哲学,其他语言对于这个问题并不一定使用这种方式。

Meyers的建议是“Make std::threads unjoinable on all paths”,也就是让std::thread在销毁时是unjoinable的。这是一种trade-off, 和之前的第一种做法一样会导致潜在的性能问题。但是相比于其他两种选择:程序被终止;detach的undefined行为,这是可以接受的(对于性能问题,可以通过实现interruptible threads来弥补)。

为了确保“Make std::threads unjoinable on all paths”,那么在函数返回和异常发生时,thread要是unjoinable状态的,所以可以用RAII来完成:

class ThreadRAII
{
public:
    enum class DtorAction { join, detach };
public:
    ThreadRAII(std::thread&& t, DtorAction act)
        :m_action(act), m_thread(std::move(t)) {}

    ~ThreadRAII()
    {
        if (m_thread.joinable())
        {
            if (m_action == DtorAction::join)
                m_thread.join();
            else
                m_thread.detach();
        }
    }

    ThreadRAII(ThreadRAII&&) = default;
    ThreadRAII& operator=(ThreadRAII&&) = default;

    std::thread& get() { return m_thread; }

private:
    std::thread m_thread;
    DtorAction m_action;
};

ThreadRAII构造函数接收std::thread rvalue,因为std::thread不可复制,调用move之后,传进来的std::thread就变成了unjoinable的,执行任务就转移给了ThreadRAII的std::thread。

有了ThreadRAII,就可以安全地使用std::thread:

void doSomething()
{
    ThreadRAII t(std::thread([]
    {
       ....
    }
    ));
    if(someCondition())
    {
        t.get().join();
        ...
    }
}

 

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:[C++学习笔记] const限定符

下一篇:快速排序_c++