C++ 并发编程学习(三)
向线程函数传递参数
一. 传参const char*
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");
函数f需要一个 std::string 对象作为第二个参数,但这里使用的是字符串的字面值,也就是 char const * 类型。之后,在线程的上下文中完成字面值向 std::string 对象的转化。
二. 传参指向动态变量的指针
void 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)); // 使用std::string,避免悬垂指针
t.detach();
}
buffer是一个指针变量,指向本地变量,然后本地变量通过buffer传递到新线程中。并且,函数有很有可能会在字面值转化成 std::string 对象之前崩溃(oops),从而导致一些未定义的行为。并且想要依赖隐式转换将字面值转换为函数期待的 std::string 对象,但因 std::thread 的构造函数会复制提供的变量,就只复制了没有转换成期望类型的字符串字面值。解决方案就是在传递到 std::thread 构造函数之前就将字面值转化为std::string 对象。
三.传参期望传递一个引用
void update_data_for_widget(widget_id w,widget_data& data); // 1
void oops_again(widget_id w){
widget_data data;
std::thread t(update_data_for_widget,w,data); // 2
display_status();
t.join();
process_widget_data(data); // 3
}
虽然update_data_for_widget的第二个参数期待传入一个引用,但是 std::thread 的构造函数并不知晓;构造函数无视函数期待的参数类型,并盲目的拷贝已提供的变量。当线程调用update_data_for_widget函数时,传递给函数的参数是data变量内部拷贝的引用,而非数据本身的引用。因此,当线程结束时,内部拷贝数据将会在数据更新阶段被销毁,且process_widget_data将会接收到没有修改的data变量。可以使用 std::ref 将参数转换成引用的形式,从而可将线程的调用改为以下形式:
std::thread t(update_data_for_widget,w,std::ref(data)
在这之后,update_data_for_widget就会接收到一个data变量的引用,而非一个data变量拷贝的引用。
四. std::move 的用法
void 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));
在 std::thread 的构造函数中指定 std::move(p) ,big_object对象的所有权就被首先转移到新创建线程的的内部存储中,之后传递给process_big_object函数。标准线程库中和 std::unique_ptr 在所属权上有相似语义类型的类有好几种, std::thread 为其中之一。虽然, std::thread 实例不像 std::unique_ptr 那样能占有一个动态对象的所有权,但是它能占有其他资源:每个实例都负责管理一个执行线程。执行线程的所有权可以在多个 std::thread 实例中互相转移,这是依赖于 std::thread 实例的可移动且不可复制性。不可复制保性证了在同一时间点,一个 std::thread 实例只能关联一个执行线程;可移动性使得程序员可以自己决定,哪个实例拥有实际执行线程的所有权。