2.2 Passing arguments to a thread function
2.2 Passing arguments to a thread function
[!quote]
It's important to bear in mind that by default, the arguments are copied into internal storage, where they can be accessed by the newly created thread of execution, and then passed to the callable object or function as rvalues as if they were temporaries.
- copied into internal storage
- rvalues
在thread的构造函数中传递参数
使用:
void f(int i, std::string const &s)
{
std::cout << "i:" << i << " s:" << s << std::endl;
}
int main()
{
std::thread t(f, 3, "hello");
t.join();
return 0;
}| 行号 | 功能 | 说明 |
|---|---|---|
| 1-4 | 线程执行的function定义 | 函数具有两个参数,第二个参数类型为const& |
| 7 | 创建线程并传递参数给线程执行的function | 参数跟在函数参数f后,按照顺序传递给f |
效果:
i:3 s:hello参数传递的过程
参数先copied into 到 thread 的internal storage,调用function 时再以右值类型传递给function。
创建MyClass 类,在类的拷贝构造、赋值,移动构造、赋值函数中增加打印,然后function 接收MyClass类对象作为参数,观察类对象的创建过程。
实现:
struct MyClass{
MyClass(){
cout << "MyClass()" << endl;
}
MyClass(const MyClass&){
cout << "MyClass(const MyClass&)" << endl;
}
//略
~MyClass(){
cout << "~MyClass" << endl;
}
};
void fc(MyClass f)
{
cout << "fc(MyClass)" << endl;
}
int main()
{
MyClass mc;
cout << __LINE__ << endl;
std::thread t1(fc, mc);
cout << __LINE__ << endl;
t1.join();
return 0;
}效果:
MyClass()
44
MyClass(const MyClass&)
46
MyClass(MyClass&&)
fc(MyClass)
~MyClass
~MyClass
~MyClass| 行号/打印内容 | 说明 |
|---|---|
| 1/MyClass | 构造mc |
| 3/MyClass(const MyClass &) | 将mc copied into internal storage |
| 5/MyClass(Myclass&&) | 线程启动,调用fc。 传递thread internal storage 中的临时对象(右值)给fc |
| 6/fc(MyClass) | 执行fc |
| 同样地,对于前面的thread t 传递参数"hello" 给 f,先是拷贝"hello" 到 internal storage,其类型为const char *, 然后线程调用 f(3, "hello") |
传递指针参数
[!quote]
This is particularly important when the arguments supplied is a pointer to an automatic variable
如果传递指针类型参数,且指针指向的是automatic variable(局部临时变量),在调用function前,指针指向的变量可能已经销毁。
实现:
void not_oops(int some_param, char **address)
{
char buffer[8];
*address = buffer;
cout << "buffer address:" << (void*)(&buffer) << endl;
sprintf(buffer, "%i", some_param);
std::thread t(f, 3, buffer);
t.detach();
}
int main()
{
char *address;
not_oops(3, &address);
cout << "address:" << (void*)(address) << " v:" << std::string(address) << endl;
while(1)
;
return 0;
}| 行号 | 功能 | 说明 |
|---|---|---|
| 3 | 创建指针 | |
| 4 | 将buffer的地址赋值给address | |
| 8 | 创建线程,传递指针参数 | thread中的临时变量类型为char* ,指向oops中的buffer |
| 9 | 分离线程 | 函数返回(退出)后automatic variable buffer 会自动销毁 |
| 18、19 | 等待子线程执行完毕 |
效果:
buffer address:0xfc757ffc54
address:0xfc757ffc54 v:
i:3 s:s输出内容随机
先打印 addres:。。。说明执行f(3,buffer)前,not_oops()已经返回(buffer 已经销毁)。在执行f()时buffer指向的内容已经发生变化。如果f()在not_oops()之前执行那么应打印 i:3 s:3
延时(sleep)让f()先执行完毕
输出:
buffer address:0x7c6cdffaa4
i:3 s:3
address:0x7c6cdffaa4 v:避免implicit conversion
如果传递的实参类型和执行的线程函数参数类型不一致,应该在创建thread 对象时进行类型转换,而不是等到线程启动前的implicit conversion
显式转换类型:
std::thread t(f, 3, std::string(buffer));输出:
buffer address:0xab225ffa28
address:0xab225ffa28 v:
i:3 s:3传递non-const reference
如果想要传递non-const reference 给线程函数,在线程函数内更新参数值。编译会报错
实现:
using widget_id = int;
using widget_data = double;
void update_data_for_widget(widget_id w, widget_data &data)
{
cout << "w:" << w << " data:" << data << endl;
data *= 2;
}
void oops_again(widget_id w)
{
widget_data data = 3.14;
std::thread t(update_data_for_widget, w, data);
t.join();
cout << "after update data:" << data << endl;
}
int main()
{
oops_again(1);
return 0;
}报错:在 10行构造thread t 处
error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues
翻译:
std::thread 的参数在转换为右值后必须可调用原因:
[!quote]
it's oblivious to the types of the arguments expected by the function and blindly copies the supplied values. But the internal code passes copied arguments as rvalues in orader to woke with move-only types, and will thus try to call update_data_for_widget with an rvalue.
在thread 的internal storage 拷贝后的参数其类型是右值类型,不能将其绑定到non-const reference,所以编译报错。
使用std::ref
如果想要在线程函数中更新参数值,需要使用std::ref()对参数进行包装
实现:
std::thread t(update_data_for_widget, w, std::ref(data));效果:
w:1 data:3.14
after update data:6.28使用类的成员函数作为线程函数
实现:
class X{
public:
void do_lengthy_work(int i) { cout << __func__ << " i:" << i << endl; }
};
int main()
{
X my_x;
std::thread t(&X::do_lengthy_work,&my_x, 3);
t.join();
return 0;
}对于类的非静态成员函数,要通过类对象进行调用。通过对象地址传递隐式的参数-this指针。如果成员函数带有参数,在对象地址后依次给定。
效果:
do_lengthy_work i:3传递禁止拷贝,可以移动的参数
对于unique_ptr 这类只能移动不可拷贝的类型,如果要将其作为线程函数的参数需要将其管理的资源移动到线程中。
示例:
using big_object = int;
void process_big_object(std::unique_ptr<big_object> up){
cout << *up.get() << endl;
}
int main()
{
std::unique_ptr<big_object> p(new big_object(42));
std::thread t(process_big_object, std::move(p));
std::thread t2(process_big_object, std::unique_ptr<big_object>(new big_object(33)));
t.join();
t2.join();
return 0;
}| 行号 | 功能 | 说明 |
|---|---|---|
| 8 | 创建一个unique_ptr,它管理一个动态分配的int对象 | |
| 10 | 将p管理的资源移动到thread t内 | 具名对象p仍然是左值,需要显式调用std::move()转换为右值。否则报错(thread 内部对左值进行拷贝,但是由于当前对象禁止拷贝,所以报错) |
| 11 | 匿名对象,可以直接传递。 | |
| 3 | 打印值 |
效果:
42
33