前言
在上一节中,所有的入口函数均为无参函数,若某个入口函数存在形参,则需要向线程函数传递参数。这一行为并不复杂,仅仅需要在函数名后加上参数即可,例如:
1 | void do_some_work(int); |
复制与引用
复制
在执行参数传递时需要注意的是,在不加以说明的情况下,所传参数必然以拷贝的形式传入线程独立内存中,即使在入口函数中形参是一个引用。举例而言:1
2void f(int i, std::string const& s);
std::thread t(f, 3, "hello");f
需要的是一个string
对象,而我们传入的是一个字面值常量,即const char *
,因此这里首先完成了字面值向string
对象的转化,并将该string
对象拷贝至t的独立存储空间,并在其后完成引用绑定。
问题实例
当指向动态变量的指针作为参数传递给线程时需要特别注意:1
2
3
4
5
6
7void f(int i,std::string const& s);
void oops(int some_param) {
char buffer[1024];
sprintf(buffer, "%i",some_param);
std::thread t(f,3,buffer);
t.detach();
}
在函数oops
中,buffer是一个指向本地char数组的指针。如果oops
函数在线程对象t构造时意外退出(buffer转换成std::string对象之前),那么依赖buffer隐式转为string的愿景将无法实现。解决方案是在std::thread
构造函数中显式地执行转换操作,确保转换发生在构造之前:1
2
3
4
5
6
7void f(int i,std::string const& s);
void not_oops(int some_param) {
char buffer[1024];
sprintf(buffer,"%i",some_param);
std::thread t(f,3,std::string(buffer)); //免悬垂指针
t.detach();
}
引用
有时我们确实需要将变量以引用,而非复制的形式传入线程,比如我们需要处理一个被线程更新过的数据:1
2
3
4
5
6
7
8void update_data_for_widget(widget_id w,widget_data& data);
void oops_again(widget_id w) {
widget_data data;
std::thread t(update_data_for_widget,w,data);
display_status();
t.join();
process_widget_data(data);
}
在上述写法中,线程结束后更新完毕的data将会被销毁,process_widget_data
处理的永远是最初的原始数据。
解决方案是采用std::ref
将参数转为引用即可,此时update_data_for_widget
将接收到data的引用,而非是data的拷贝的引用,如
1 | std::thread t(update_data_for_widget,w,std::ref(data)); |
以成员函数作为入口函数
我们可以认为,成员函数本身也是一种普通函数,其特殊之处在于其第一个形参是this
指针。因此,如下写法也不难理解:1
2
3
4
5
6
7class X {
public:
void do_lengthy_work(int);
};
X my_x;
int num(0);
std::thread t(&X::do_lengthy_work, &my_x, num);
移动
入口函数同样支持移动传入的变量。具体而言,临时变量总是以移动形式传入,而命名变量则需要显式地使用std::move
:1
2
3
4void process_big_object(std::unique_ptr<big_object>);
std::unique_ptr<big_object> p(new big_object);
p->prepare_data(42);
std::thread t(process_big_object,std::move(p));
thread的移动性
std::thread
类似于unique_ptr
,也是一种move only object
。一个std::thread
持有一个执行线程,执行线程的所有权可以在多个std::thread
间转移。std::thread
的不可复制性保证了在同一时间点,一个std::thread
仅可持有一个执行线程,而其移动性则保证了程序撰写者可以决定哪个std::thread
对象可以拥有执行线程的所有权。