2.1 线程管理基础
包含头文件:
#include<iostream>
#include<thread>
一、创建线程:
- 利用入口函数直接创建
thread my_thread(my_thread_function);
- 将类作为入口函数
class My_thread{
public:
void operater()() const{
//do something
}
};
My_thread f;
thread my_thread(f);
- 使用类的成员函数:
class A{
public:
My_thread_function(){}
};
A a;
thread my_thread(&A::My_thread_function,&a);
- 使用lambda表达式:
std::thread my_thread([]{
do_something();
do_something_else();
});
一旦创建了线程,线程立即执行;故而还需设定子线程和主线程的关系(join or detach?);
若主线程不等待子线程(detach),则需要确保在子线程结束前,可访问的数据得有效性。
二、 detach 和 join
- detach(不等待模式):
my_thread.detach();
-
主线程先于子线程结束,使得子线程中某些变量失去意义;
-
解决方案:使线程函数的功能齐全,将数据复制到线程中,而非复制到共享数据中
-
join(等待模式):
my_thread.join()
-
主线程等待子线程结束后,才继续执行;确保主线程不会先于子线程结束。
-
异常等待:当在线程运行之后产生异常,在join()调用之前抛出,就意味着这次调用会被跳过。当倾向于在无异常的情况下使用join()时,需要在异常处理过程中调用join(),从而避免生命周期的问题。
-
例子:
struct func; // 定义在清单2.1中
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
}
说明:
try/catch能够捕获轻量级错误;
当函数正常退出时,会执行到2处;当函数执行过程中抛出异常,程序会执行到1处。
2.2 向线程函数传递参数
thread my_thread(my_thread_function, function_param);
- 若后续使用detach,则需要特别注意输入的参数:千万不可隐式的转化类型,可能在转化成功前(未给线程)就发生崩溃。
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();
}
- 若传递引用,需要使用 std::ref(some_param),否则会全部复制:
thread my_thread(my_thread_function, ref(my_param));
2.3 转移线程所有权
假设要写一个在后台启动线程的函数,想通过新线程返回的所有权去调用这个函数,而不是等待线程结束再去调用;或完全与之相反的想法:创建一个线程,并在函数中转移所有权,都必须要等待线程结束。总之,新线程的所有权都需要转移。
std::ifstream, std::unique_ptr 还有std::thread 都是可移动,但不可拷贝。
void some_function();
void some_other_function();
std::thread t1(some_function);
std::thread t2=std::move(t1); // t1的所有权就转移给了t2。之后,t1和执行线程已经没有关联了
t1=std::thread(some_other_function); // 与一个临时std::thread对象相关的线程启动
std::thread t3; // 创建但与任何执行线程都没有关联
t3=std::move(t2); // t2的所有权转移给t3
t1=std::move(t3); // 赋值操作将使程序崩溃
最后一个移动操作,将some_function线程的所有权转移给t1。不过,t1已经有了一个关联的线程(执行some_other_function的线程),所以这里系统直接调用 std::terminate() 终止程序继续运行。
实例:量产线程,等待它们结束
void do_work(unsigned id);
void f()
{
std::vector<std::thread> threads;
for(unsigned i=0; i < 20; ++i)
{
threads.push_back(std::thread(do_work,i)); // 产生线程
}
std::for_each(threads.begin(),threads.end(),
std::mem_fn(&std::thread::join)); // 对每个线程调用join()
}
2.4 运行时决定线程数量
std::thread::hardware_concurrency()
这个函数将返回能同时并发在一个程序中的线程数量。例如,多核系统中,返回值可以是CPU核芯的数量。返回值也仅仅是一个提示,当系统信息无法获取时,函数也会返回0。但是,这也无法掩盖这个函数对启动线程数量的帮助。
2.5 识别线程
std::this_thread::get_id();
可得到当前线程的id;