线程管理的基础
一. 启用线程
- 线程的使用需要头文件<thread>,以及在编译的时候需要使用 -lpthread;
- 起线程时初始函数为普通函数时,第一个参数直接给函数名,后面可携带多个参数;
- 起线程时初始函数为一个函数调用操作符重载的类时,可使用以下三种发放进行调用,后面可携带多个参数;
- 无论如何,确保线程正确的加入(joined)或分离(detached),必须在 std::thread 对象销毁之前做出决定,否则程序将终止(std::thread的析构函数会调用std::terminate());
#include <iostream>
#include <thread>
class background_task{
public:
void operator()(int a) {
do_something();
do_something_else();
std::cout << "a = " << 9 <<std::endl;
}
void do_something(){
std::cout << "do_something()" << std::endl;
};
void do_something_else(){
std::cout << "do_something_else()" << std::endl;
};
};
void task_fun(int a){
std::cout << "task_fun(): "<< a << std::endl;
return ;
}
int main(){
// method 1:
background_task f;
std::thread my_thread(f, 9);
std::thread my_thread2(task_fun,9);
// method 2:
// std::thread my_thread((background_task()));
// std::thread my_thread2((task_fun())); //x 错误的使用方法
// method 3:
// std::thread my_thread{background_task()};
// std::thread my_thread2{task_fun()}; //x 错误的使用方法
my_thread.join();
my_thread2.join();
return 0;
}
/*程序输出 并发进行,两个线程同时打印,所以顺序乱了
task_fun(): 9do_something()
do_something_else()
a = 9
*/
join()是简单粗暴的等待线程完成或不等待。当你需要对等待中的线程有更灵活的控制时,比如,看一下某个线程是否结束,或者只等待一段时间(超过时间就判定为超时)。想要做到这些,你需要使用其他机制来完成,比如条件变量和期待(futures)。调用join()的行为,还清理了线程相关的存储部分,这样 std::thread 对象将不再与已经完成的线程有任何关联。这意味着,只能对一个线程使用一次join();一旦已经使用过join(),std::thread 对象就不能再次加入了,当对其使用joinable()时,将返回false。
二. 特殊情况下的等待
#include <iostream>
#include <thread>
#include <unistd.h>
class thread_guard
{
public:
explicit thread_guard(std::thread& t_):t(t_){}
~thread_guard(){
if (t.joinable()){
t.join();
}
}
thread_guard(thread_guard const&)=delete;
thread_guard& operator=(thread_guard const&)=delete;
private:
std::thread& t;
};
void task_fun(int *a){
sleep(10);
std::cout << "task_fun(): "<< *a << std::endl;
return ;
}
int main()
{
int a = 100;
std::thread t(task_fun, &a);
thread_guard g(t);
std::cout << "主线程已经结束" << std::endl;
return 0;
}
/*程序输出
主线程已经结束
task_fun(): 100 //十秒后
*/
如上程序,如果把thread_guard的析构函数的内容注释掉,那么在程序结束了,可局部变量a还在线程中被使用,这样就会导致析构函数调用std::terminate() ,从而程序崩溃。如上方式可以很好的去防止线程主线程结束后,等待子线程结束任务。拷贝构造函数和拷贝赋值操作被标记为 =delete,是为了不让编译器自动生成它们。通过删除声明,任何尝试给thread_guard对象赋值的操作都会引发一个编译错误。
三. 后台运行线程
使用detach()会让线程在后台运行,这就意味着主线程不能与之产生直接交互。也就是说,不会等待这个线程结束;如果线程分离,那么就不可能有 std::thread 对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。
为了从 std::thread 对象中分离线程(前提是有可进行分离的线程),不能对没有执行线程的 std::thread 对象使用detach(),也是join()的使用条件,并且要用同样的方式进行检查——当 std::thread 对象使用t.joinable()返回的是true,就可以使用t.detach()。
网友评论