主要内容
- 启动新线程
- 等待线程与分离线程
- 线程唯一标识符
2.1.1 线程启动
线程启动可以归结为构造std::thread对象。
1.传入函数
#include <thread>
void function();
std::thread my_thread(function);
2.传入类对象
#include <thread>
class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
代码中, 提供的函数对象会复制到新线程的存储空间当中, 函数对象的执行和调用都在线程
的内存空间中进行。 函数对象的副本应与原始函数对象保持一致, 否则得到的结果会与我们
的期望不同。
3.传入临时对象
编译器会将他定义为声明一个my_thread的函数,函数有一个参数(函数指针指向没有参数并返回background_task对象的函数),返回一个std::thread对象的函数。
std::thread my_thread(background_task());//传递一个临时变量,编译器会将解析为函数声明。
/解决方法//
std::thread my_thread((background_task()));
std::thread my_thread{background_task()};
2.1.2 join(等待线程完成)
void loop1()
{
for(int i = 0; i < 20; i++)
{
std::cout << 1 << std::endl;
}
}
void loop2()
{
for (int i = 0; i < 20; i++)
{
std::cout << 2 << std::endl;
}
}
void callback()
{
std::thread loop1_function(loop1);
std::thread loop2_function(loop2);
loop2_function.join();
loop1_function.join();
for(int i = 0; i < 20; i++)
{
std::cout << 3 << std::endl;
}
}
int main()
{
callback();
return 0;
}
原始线程等待启动的子线程结束工作,然后开始工作。
执行结果:1和2交替输出,3最后输出。
线程等待可以确保局部变量在线程完成时,才被摧毁。
一个线程只能使用一次join(),一旦使用过一次join(),std::thread对象就不能再次加入,当对其使用joinable()时,将返回false。
2.1.3 特殊情况下的等待
声明线程后没有直接join(),想在其他方法中调用线程的join();
Q:线程运行之后产生异常,在join()调用之前抛出,join()将不会被调用。
A1: 需要再异常处理中调用join()try只能捕获轻量级错误
struct func; // 一个循环方法
void f()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
try
{
do_something_in_current_thread();
}
catch(...)
{
t.join(); // 1
throw;
}
t.join(); // 2
}
A2:析构函数中使用join()
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::thread& t_):
t(t_){}
~thread_guard()
{
if(t.joinable()) // 1
{
t.join(); // 2
}
}
thread_guard(thread_guard const&)=delete; // 3
thread_guard& operator=(thread_guard const&)=delete;
};
struct func; // 定义在清单2.1中
void f()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
} // 4
2.1.4 detach 后台运行线程
detach分离后的线程会在后台运行,主线程不会等待这个线程结束。
应用场景:后台监视文件系统, 对缓存进行清理, 亦或对数据结构进行优化
使用条件:
struct func(){}
std::thread t1(func);
if( t1.joinable() )//若返回true,表明函数可以加入线程
{
t1.detach();
}
2.2 向线程传递参数
1. 传递对象和成员函数
class x
{
public:
void do_something();
}
x my_x;
std::thread t(&x::do_something, &my_x);//传递对象
x my_x1;
int num(0);
std::thread t(&x::do_something, &my_x, num);//传递对象及成员函数
2. 传递指向动态变量的指针(注意)
void f(int i, std::string const& s);
void oops(int some_param)
{
char buffer[1024];
sprintf(buffer, "%i", somr_param);
std::thread t(f, 3, buffer);//很大可能会在字面值转为std::thread对象前崩溃
t.detach();
}
key question: std::thread的构造函数会复制提供的变量,就只复制了没有转换成期望类型的字符串字面值。
解决办法:
std::thread t(f,3,std::string(buffer));
3. 传递一个引用
void upadte_data_for_widget(widget_id w, widget_data& data);
void oops_again(widget_id w)
{
widget_data data;
std::thread t(updata_data_for_widget, w, data);
display_status();
t.join();
process_widget_data(data);
}
key question : 传递给函数的参数是data变量内部的拷贝引用,而非数据本身的引用,内部拷贝数据将会在数据更新阶段被销毁。
换成这一句将会解决
std::thread t(update_data_for_widget,w ,std::ref(data));