28 June 2023

C++ 核心指南目录

“Think of a thread as a global container”

理由

为了确保指针安全,避免泄露,我们要考虑线程中用到了什么指针。如果线程被分离(detach),我们就能安全的把指针传递给静态自由存储区对象。

例子

void f(int* p)
{
    // ...
    *p = 99;
    // ...
}

int glob = 33;

void some_fct(int* p)
{
    int x = 77;
    std::thread t0(f, &x);           // bad
    std::thread t1(f, p);            // bad
    std::thread t2(f, &glob);        // OK
    auto q = make_unique<int>(99);
    std::thread t3(f, q.get());      // bad
    // ...
    t0.detach();
    t1.detach();
    t2.detach();
    t3.detach();
    // ...
}

这里的 OK 指的是只要线程用到指向这些对象的指针,这些对象都在作用域范围内。这里 bad 指的是一个线程可能会用到一个指针,而这个指针所指向的对象已经被销毁。线程并行运行并不影响这些指针的生存周期或者所有权。我们可以把这些线程当作是在 some_fct 中调用的函数对象。

注意

在分离的线程中使用静态存储的对象也可能存在问题:如果线程在程序结束之后,还在运行,可能会在对象销毁的时候同时在运行,这就出现访问该对象的竞争条件。

注意

如果你不用 detach() 而且用 gsl::joining_thread 这条规则就显得有些多余了。不过把这里的代码转换成遵循那些指南规则的代码可能比较麻烦,或者甚至在某些第三方库中不太可能。在这种情况下,为了满足生存周期安全和类型安全来说,这条规则就很有必要。

一般来说,无法判断是否执行了一个线程的 detach() ,不过一些简单案例还是很容易判断的。如果我们无法证明一个线程没 detach() ,我们必须假定它会被分离,继续在它被构建的范围之外存活。这样,就要针对线程使用到的对象的生存周期和所有权进行强化。

强化

标记可能会被 detach() 的线程使用了局部变量的情况。