线程参数传递要记住一个重要的事情,传递的参数是存在新线程一个内部的转存站中,之后在函数执行的时候再传递给函数本身的。
这种机制会引发两个问题:
1.临时参数的未及时构造
void 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); //这里的buffer指向的是一个栈上的指针,所以在线程真正要执行这个函数f的时候,这个栈可能已经释放了,因此会存在异常
t.detach();
}
虽然函数f的第二个参数接受的是一个std::string,但是我们传递进去的是一个char*,而这个char*在创建这个线程的时候并不会直接就转换为std::string.这里出现的问题就是调用时机与传递参数的时机不一致所导致的,所以一种更安全的写法是先把这个string构造好:
void 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,std::string(buffer)); //这里首先将buffer构造成string传进去
t.detach();
}
这种做法保证了现将buffer构造好string,随后线程传递的时候传递string就不会有这个问题。
2.传递引用时无法修改引用的数据
void update_data_for_widget(widget_data& data);//函数接受的参数是引用
void oops_again()
{
widget_data data;
std::thread t(update_data_for_widget,data);//试图在异步线程中修改data
t.jion();
process_widget(data);
}
虽然update_data_for_widget这个函数接受的是引用,可以修改data的值。但是当我们把data传入std::thread的构造函数时,拷贝构造就已经发生了。也就是说,最后update_data_for_widget这个函数处理的只是一份拷贝,根本就不会修改原始的值。所以在这种场景下,我们可以采用以std::ref的形式传递引用。相当于在引用上面做了一层对象封装,单纯的传递对象依旧可以修改原始的值:
std:thread t(update_data_for_widget,std::ref(data));
另外,线程如果像传递一个成员函数作为参数,其形式类似于bing的形式:
class X
{
public:
void do_lengthy_work();
}
x my_x;
std::thread t(&X::do_lengthy_work,&my_x);
对于参数有std::move的需求,对于临时变量将会自动调用,对于有名字的变量我们可以采用std::move()来要求执行。
同样的,std::thread对象是一个只可以移动,不可以拷贝的对象。这里注意一个场景:如果一个对象已经被初始化后,又被赋予其他的对象,就会产生问题:
std::thread t1(test_function);
std::thread t2(test_function);
t1 = std::move(t2);
这里执行的第三句是程序就会异常退出,因为t1已经有了对象并且在释放这个线程前没有调用任何的detach() or join().