CppCoreGuidelines CP.24 把一个线程当作是全局的容器
28 June 2023
“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()
的线程使用了局部变量的情况。