C++编程指南20 - 使用 joining_thread以确保线程不会在变量生命周期之外运行
一:概述
在多线程编程中,如果一个线程需要访问外部作用域的变量,就必须保证这个变量在 线程结束之前依然有效,否则可能会出现 悬空指针(dangling pointer) 的问题,导致 未定义行为。
二:如何解决?
使用 joining_thread
(类似 std::thread
,但会在析构时自动 join()
,且不能 detach()
)可以避免这种问题,因为:
- 线程的生命周期受到管理:不会意外地
detach()
导致资源泄漏。 - 作用域内对象的生命周期受控:确保线程所访问的对象在其执行期间是有效的。
三:代码示例
#include <iostream>
#include <thread>
#include <memory>
void f(int* p) {
*p = 99;
}
int glob = 33;
void some_fct(int* p) {
int x = 77;
std::thread t0(f, &x); // ✅ OK:x 在作用域内
std::thread t1(f, p); // ✅ OK:p 是外部传入的
std::thread t2(f, &glob); // ✅ OK:glob 是全局变量
auto q = std::make_unique<int>(99);
std::thread t3(f, q.get()); // ❌ 错误!q 是局部变量,t3 可能在 q 析构后访问无效地址
t0.join();
t1.join();
t2.join();
t3.join();
}
以上 t3(f, q.get());
可能会导致 悬空指针,因为 q
在 some_fct()
结束时会被销毁,而 t3
可能仍然在运行,这样 f()
就可能访问已释放的内存,导致 未定义行为。
解决办法:使用 joining_thread
(概念类似 std::thread
,但会在析构时自动 join()
,确保线程不会在变量生命周期之外运行)
#include <iostream>
#include <thread>
#include <memory>
class joining_thread {
std::thread t;
public:
template <typename... Args>
explicit joining_thread(Args&&... args) : t(std::forward<Args>(args)...) {}
~joining_thread() {
if (t.joinable()) {
t.join();
}
}
joining_thread(const joining_thread&) = delete;
joining_thread& operator=(const joining_thread&) = delete;
};
void f(int* p) {
*p = 99;
}
int glob = 33;
void some_fct(int* p) {
int x = 77;
joining_thread t0(f, &x); // ✅ OK:x 在作用域内
joining_thread t1(f, p); // ✅ OK:p 是外部传入的
joining_thread t2(f, &glob); // ✅ OK:glob 是全局变量
auto q = std::make_unique<int>(99);
joining_thread t3(f, q.get()); // ✅ OK:q 仍然在作用域内
// 这里不需要手动 join(),析构时会自动 join()
}