转移所有权
线程所有权通过移动语义转移,确保唯一管理,避免资源冲突和程序崩溃。
转移所有权
转移所有权
在 C++ 中,std::thread
是一个资源占有类型,它的行为类似于 std::unique_ptr
:可移动,但不可复制。这样设计的目的,是为了确保线程的所有权始终只有一个明确的拥有者,避免多线程并发错误。
为什么要支持移动?
当我们创建线程并希望将线程的所有权传递给其他对象(比如函数返回值、容器等)时,必须通过移动操作来实现。
示例:线程所有权的转移
1
2
3
4
5
6
7
8
9
10
11
void some_function();
void some_other_function();
std::thread t1(some_function); // 1 创建并启动线程
std::thread t2 = std::move(t1); // 2 转移所有权到 t2
t1 = std::thread(some_other_function); // 3 启动另一个线程,隐式移动到 t1
std::thread t3; // 4 默认构造,无线程
t3 = std::move(t2); // 5 t2 的线程所有权转移到 t3
t1 = std::move(t3); // 6 ❌ 错误:t1 已拥有线程,导致 std::terminate()
t1
在① 时拥有线程,② 后t1
不再拥有线程,t2
拥有。- ③ 中创建的是临时
std::thread
,可直接移动给t1
。 - ⑤ 中使用
std::move(t2)
明确地将t2
的线程所有权转移给t3
。 - ⑥ 尝试将
t3
的线程所有权赋值给已拥有线程的t1
,违反规则,程序会触发std::terminate()
。
示例:函数返回 std::thread
1
2
3
4
5
6
7
8
9
10
std::thread f() {
void some_function();
return std::thread(some_function); // 返回临时线程对象,发生隐式移动
}
std::thread g() {
void some_other_function(int);
std::thread t(some_other_function, 42);
return t; // 返回局部变量,触发移动构造
}
示例:将线程传入函数
1
2
3
4
5
6
7
8
9
void f(std::thread t); // 参数通过移动方式获取线程所有权
void g() {
void some_function();
f(std::thread(some_function)); // 传入临时对象,隐式移动
std::thread t(some_function);
f(std::move(t)); // 显式移动已有线程对象
}
scoped_thread —— 防止线程泄露
定义
scoped_thread
是一个自定义类,它不是 C++ 标准库自带的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class scoped_thread {
std::thread t; // 成员变量,拥有一个 std::thread 实例,负责管理一个线程
public:
// 显式构造函数:通过移动构造的方式接收一个线程对象
explicit scoped_thread(std::thread t_)
: t(std::move(t_)) // 移动线程所有权到成员变量 t
{
// 如果传入的线程不可 join(说明它不是一个有效线程),抛出逻辑错误异常
if (!t.joinable())
throw std::logic_error("No thread");
}
// 析构函数:当 scoped_thread 对象销毁时,自动 join 所管理的线程
~scoped_thread() {
t.join(); // 等待线程执行结束,防止主线程结束后线程未完成
}
// 禁用拷贝构造函数,防止复制 scoped_thread(因为线程只能唯一拥有)
scoped_thread(const scoped_thread&) = delete;
// 禁用拷贝赋值操作,防止复制赋值
scoped_thread& operator=(const scoped_thread&) = delete;
};
用法示例
1
2
3
4
5
6
7
struct func; // 假设定义在其他地方
void f() {
int some_local_state;
scoped_thread t(std::thread(func(some_local_state))); // 线程交给 scoped_thread 管理
do_something_in_current_thread();
} // 离开作用域自动 join
相比手动 join
,使用 RAII 的方式可以自动管理线程生命周期,避免资源泄露。
C++20:建议加入 std::jthread
由于 scoped_thread
的思想太有用,C++20 引入了 std::jthread
类型,功能类似于:
joining_thread 示例实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 一个封装 std::thread 的类,支持 RAII 管理线程,析构时自动 join
class joining_thread {
std::thread t; // 内部持有的 std::thread 对象,代表一个可执行线程
public:
// 默认构造函数,线程未初始化
joining_thread() noexcept = default;
// 构造函数:接受任意可调用对象及其参数,完美转发给 std::thread 构造线程
template<typename Callable, typename... Args>
explicit joining_thread(Callable&& func, Args&&... args)
: t(std::forward<Callable>(func), std::forward<Args>(args)...) {}
// 构造函数:从已有的 std::thread 对象中移动构造
explicit joining_thread(std::thread t_) noexcept : t(std::move(t_)) {}
// 移动构造函数:支持 joining_thread 之间移动所有权
joining_thread(joining_thread&& other) noexcept : t(std::move(other.t)) {}
// 移动赋值操作:如果当前对象持有线程,则先 join,避免线程泄漏
joining_thread& operator=(joining_thread&& other) noexcept {
if (joinable()) { // 当前线程有效,先等它执行完
join();
}
t = std::move(other.t); // 接管另一个线程的所有权
return *this;
}
// 接受 std::thread 作为右值赋值:先 join 当前线程,然后接管新线程
joining_thread& operator=(std::thread other) noexcept {
if (joinable()) {
join();
}
t = std::move(other);
return *this;
}
// 析构函数:如果线程有效且尚未 join,则自动 join
~joining_thread() noexcept {
if (joinable())
join();
}
// 交换两个 joining_thread 对象所持有的线程(成员 swap)
void swap(joining_thread& other) noexcept {
t.swap(other.t);
}
// 返回当前线程的 ID
std::thread::id get_id() const noexcept {
return t.get_id();
}
// 判断线程是否可以 join(即线程是否仍然活跃)
bool joinable() const noexcept {
return t.joinable();
}
// 手动调用 join,等待线程结束
void join() {
t.join();
}
// 手动调用 detach,使线程在后台执行并与当前对象解绑
void detach() {
t.detach();
}
// 获取对内部 std::thread 的引用(用于底层访问)
std::thread& as_thread() noexcept {
return t;
}
// 获取对内部 std::thread 的 const 引用(用于只读访问)
const std::thread& as_thread() const noexcept {
return t;
}
};
设计要点:
特性 | 说明 |
---|---|
RAII 自动管理线程 | 析构时自动 join,避免忘记调用造成崩溃 |
支持移动,不支持复制 | 保证线程所有权唯一(默认 std::thread 不可复制) |
与原生 std::thread 互操作 | 提供 as_thread() 可访问底层线程对象 |
安全移动赋值 | 赋值前自动 join 当前线程,避免资源泄漏或未定义行为 |
示例:std::thread
vs std::jthread
使用 std::thread
(C++11 起)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <thread>
#include <chrono>
void worker_thread() {
std::cout << "std::thread 工作线程开始\n";
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "std::thread 工作线程结束\n";
}
void std_thread_demo() {
std::thread t(worker_thread);
// 如果忘记 join,会崩溃!
if (t.joinable()) {
t.join();
}
}
使用 std::jthread
(C++20 起)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <thread>
#include <chrono>
#include <stop_token> // C++20 中用于取消支持
#include <functional>
void worker_jthread(std::stop_token st) {
std::cout << "std::jthread 工作线程开始\n";
for (int i = 0; i < 5; ++i) {
if (st.stop_requested()) {
std::cout << "std::jthread 收到取消请求,提前退出\n";
return;
}
std::cout << "std::jthread 执行中...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::cout << "std::jthread 工作线程结束\n";
}
void jthread_demo() {
std::jthread t(worker_jthread);
// 主线程等一会后请求取消
std::this_thread::sleep_for(std::chrono::milliseconds(1200));
t.request_stop(); // 向线程发送取消信号
// 不需要手动 join,自动完成
}
- 隐式注入
stop_token
参数机制:标准库内部会自动为函数添加一个std::stop_token
参数并传进去,只要函数的第一个参数类型正好是std::stop_token
。
输出示例
1
2
3
4
5
6
7
std::thread 工作线程开始
std::thread 工作线程结束
std::jthread 工作线程开始
std::jthread 执行中...
std::jthread 执行中...
std::jthread 收到取消请求,提前退出
在容器中管理线程
可以把多个 std::thread
存入 std::vector
,实现统一创建和管理。
示例:创建多个线程并等待它们结束
1
2
3
4
5
6
7
8
9
10
11
12
13
void do_work(unsigned id);
void f() {
std::vector<std::thread> threads;
for (unsigned i = 0; i < 20; ++i) {
threads.emplace_back(do_work, i); // 通过 emplace_back 直接构造线程
}
for (auto& t : threads) {
t.join(); // 统一等待所有线程结束
}
}
适用场景:任务划分明确、互不依赖的线程,适合批量并发执行。
总结
内容 | 说明 |
---|---|
std::thread 可移动不可复制 | 保证线程所有权唯一性 |
std::move(thread) | 显式转移所有权,防止悬空或重复管理 |
scoped_thread | RAII 风格线程管理,确保 join |
joining_thread | C++20 建议替代方案,更通用 |
容器管理线程 | 使用 std::vector<std::thread> 统一管理 |
本文由作者按照 CC BY 4.0 进行授权