RAII线程管理

每个 std::thread 对象都处于可合并(joinable)或不可合并(unjoinable)的状态。一个可合并的 std::thread 对应于一个底层异步运行的线程,若底层线程处于阻塞、等待调度或已运行结束的状态,则此 std::thread 可合并,否则不可合并。不可合并的 std::thread 包括:

如果可合并的 std::thread 对象的析构函数被调用,则程序的执行将终止:

void f() {}

void g()
{
    std::thread t(f); //@ t.joinable() == true
}

int main()
{
    g(); //@ g运行结束时析构t,导致整个程序终止
    ...
}

析构可合并的 std::thread 时,隐式 join 或隐式 detach 的带来问题更大。隐式 join 导致 g 运行结束时仍要保持等待 f 运行结束,这就会导致性能问题,且调试时难以追踪原因。隐式 detach 导致的调试问题更为致命:

void f(int&) {}

void g()
{
    int i = 1;
    std::thread t(f, i);
} //@ 如果隐式detach,局部变量i被销毁,但f仍在使用局部变量的引用

完美销毁一个可合并的 std::thread 十分困难,因此规定销毁将导致终止程序。要避免程序终止,只要让可合并的线程在销毁时变为不可合并状态即可,使用 RAII 手法就能实现这点。

class A {
public:
    enum class DtorAction { join, detach };
    A(std::thread&& t, DtorAction a) : action(a), t(std::move(t)) {}
    ~A()
    {
        if (t.joinable())
        {
            if (action == DtorAction::join) t.join();
            else t.detach();
        }
    }
    A(A&&) = default;
    A& operator=(A&&) = default;
    std::thread& get() { return t; }
private:
    DtorAction action;
    std::thread t;
};
void f() {}

void g()
{
    A t(std::thread(f), A::DtorAction::join); //@ 析构前使用join
}

int main()
{
    g(); //@ g运行结束时将内部的std::thread置为join,变为不可合并状态
    //@ 析构不可合并的std::thread不会导致程序终止
    //@ 这种手法带来了隐式join和隐式detach的问题,但可以调试
    ...
}