前言
在上一节中我们已经提及std::thread
对象是一个move only object
,而move的过程也正是转移线程所有权的过程。本节将对如何转移线程所有权,以及转移线程所有权的实际应用场景作出简单介绍。
转移实例
赋值
下述实例创建了两个执行线程,并且展示了如何在std::thread
实例(t1,t2和t3)之间转移所有权:1
2
3
4
5
6
7
8void some_function();
void some_other_function();
std::thread t1(some_function);
std::thread t2=std::move(t1);
t1=std::thread(some_other_function);// 临时对象隐式触发了移动
std::thread t3;
t3=std::move(t2);
t1=std::move(t3); // 崩溃
值得注意的是最后一个移动操作,该操作试图将t3拥有的执行线程转交t1,但由于t1已拥有执行线程,因此系统将调用terminate()
,强行终止程序运行。之前我们已经提过,需要在std::thread
对象析构前显式地join
或detach
,赋值操作亦是如此,不存在通过赋值来“丢弃”某个运行线程的概念。
函数外传递
类似地,线程所有权也可以在函数外传递,例如将std::thread
对象作为返回值:1
2
3
4
5
6
7
8
9std::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;
}std::thread
也可以作为形参:1
2
3
4
5
6
7void f(std::thread t);
void g() {
void some_function();
f(std::thread(some_function));
std::thread t(some_function);
f(std::move(t));
}
应用场景
RAII
为了保证线程总能被等待,我们可以创建RAII对象scoped_thread
,该对象持有一个std::thread
,和之前所提及的thread_guard
不同的是,这里将在构造函数中作出检查,如果某个线程不可加入,则抛出异常:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class scoped_thread {
std::thread t;
public:
explicit scoped_thread(std::thread t_):t(std::move(t_)) {
if(!t.joinable())
throw std::logic_error(“No thread”);
}
~scoped_thread() {
t.join();
}
scoped_thread(scoped_thread const&)=delete;
scoped_thread& operator=(scoped_thread const&)=delete;
};
struct func;
void f() {
int some_local_state;
scoped_thread t(std::thread(func(some_local_state)));
do_something_in_current_thread();
}
在f函数结束时,scoped_thread
析构函数触发,执行对线程的等待。
自动化管理
std::thread
也可以被装入容器(如果容器是移动敏感的),这意味着我们可以批量生产一些线程:1
2
3
4
5
6
7
8void do_work(unsigned id);
void f() {
std::vector<std::thread> threads;
for(unsigned i=0; i < 20; ++i) {
threads.push_back(std::thread(do_work,i));
}
std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join));
}
将std::thread
置入std::vector
是迈向线程自动化管理的第一步,这一行为揭示了在实际应用中我们无需为每一个std::thread
创建独立的对象,可以通过批量操作的形式完成构造与加入(join
)。