Skip to content

Latest commit

 

History

History
125 lines (99 loc) · 3.63 KB

File metadata and controls

125 lines (99 loc) · 3.63 KB

条款37:使用std::thread类型对象在所有路径皆不可join

std::thread 的joinable之所以重要的原因之一是:如果可以被join的线程对象的析构函数被调用,则程序的执行就终止了。

假设我们有一个函数 doWork ,它接收一个筛选器函数 filter 和一个最大值 maxVal 作为形参。 doWork 会校验它做计算的条件全部成立,而后会针对筛选器选出的0到 maxVal 之间的值实施计算。如果筛选是费事的,而条件检验也是费事的,那么并发地执行就是合理的:

constexpr auto tenMillion = 10'000'000;

bool doWork(std::function<bool(int)>filter,
            int maxVal = tenMillion)
{
    std::vector<int> goodVals;

    std::thread t([&filter, maxVal, &goodVals]
                  {
                    for (auto i = 0; i <= maxVal; ++i)
                    { if (filter(i)) goodVals.push_back(i); }
                  });

    auto nh = t.native_handle();
    ...
    if (conditionsAreSatisfied()) {
        t.join();
        performComputation(goodVals);
        return true;
    }
    return false;
}

如果 conditionsAreSatisfied() 返回 false 或抛出异常,那么 doWork 的末尾调用 std::thread 类型对象 t 的析构函数时,它是joinable的,从而导致程序执行终止。

如果你使用了 std::thread 对象,就得确保从它定义的作用域出去的任何路径,使它成为非joinable的。最好的方法是使用RAII技法。C++20提供了 std::jthread ,在线程对象被销毁时自动 join 线程。我们也可以自己实现:

class ThreadRAII {
public:
    enum class DtorAction { join, detach };

    ThreadRAII(std::thread&& t, DtorAction a)
    : action(a), t(std::move(t)) {}
    
    ~ThreadRAII()
    {
        if (t.joinable()) {
            if (action == DtorAction::join) {
                t.join();
            } else {
                t.detach();
            }
        }
    }

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

private:
    DtorAction action;
    std::thread t;
};

虽然在这里不会产生什么影响,但是建议将 std::thread 对象在成员列表的最后声明。

doWork 中使用 ThreadRAII

bool doWork(std::function<bool(int)>filter,
            int maxVal = tenMillion)
{
    std::vector<int> goodVals;

    ThreadRAII t(
        std::thread([&filter, maxVal, &goodVals]
                  {
                    for (auto i = 0; i <= maxVal; ++i)
                    { if (filter(i)) goodVals.push_back(i); }
                  }),
                  ThreadRAII::DtorAction::join
    );

    auto nh = t.get().native_handle();
    ...
    if (conditionsAreSatisfied()) {
        t.get().join();
        performComputation(goodVals);
        return true;
    }
    return false;
}

条款39会展示,使用 ThreadRAIIstd::thread 析构中实施 join 不仅会导致性能异常,还会导致程序无响应。解决方案是和异步执行的lambda表达式通信,这里的实现太过复杂,不做讨论。

显式声明移动操作对于 ThreadRAII 是合适且必要的:

class ThreadRAII {
public:
    enum class DtorAction { join, detach };

    ThreadRAII(std::thread&& t, DtorAction a)
    : action(a), t(std::move(t)) {}
    
    ~ThreadRAII()
    {
        if (t.joinable()) {
            if (action == DtorAction::join) {
                t.join();
            } else {
                t.detach();
            }
        }
    }

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

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

private:
    DtorAction action;
    std::thread t;
};