C++11笔记-多线程-低层接口Thread
作者:互联网
在C++11笔记-多线程-初识高级接口async()和Future中认识了C++11多线程编程的高级接口以及简单的使用;
除了高级接口async和future,C++标准库还提供了一个启动及处理线程的底层接口;
下面就开始今天的学习笔记:
Thread
std::thread的对象是用来启动和表现线程。这些对象和操作系统提供的线程呈现一对一映射关系。
thread object和线程之间的关联始于将一个callable object指派给thread object作为初值(或是move/copy assign)给它,并夹带可能有的实参。这个关联的结束会有两种不同的情况:1.由join()等待线程成果或者结束;2.由detach()分离线程和主线程之间关联,主线程将失去thread object关联的线程,即thread object表示的线程对象不受任何人控制;不论哪一个函数都必须在thread object生命结束前或在一个新的thread object被move assigned之前被调用,否则程序就会因std::terminate()而中止;
如果thread object关联至某个线程,它就是所谓join able(可连接的)。在此情况下调用joinable()会获得true,调用get_id()会获得thread ID,其值不同于std::thread::id()所得。std::thread::id的构造函数会产生一个独一无二的ID用以表示“非线程”。如果没有任何线程被关联,调用thread::get_id()获得的便是那个特殊值,如果有关联某个线程,那么调用get_id()会获得独一无二thread ID;
关于thread ID:
1.唯一可对thread ID执行的操作就是对它们进行比较,或是将它们写至一个output stream。
2.一个已结束的线程的thread ID可被系统取回重复运用;
thread的操作函数
操作 | 效果 |
---|---|
thread t | Default构造函数,创建一个nonjoinable thread object |
thread f(f,…) | 创建一个thread object,表示f将被启动于一个线程中(也许带有实参),或是抛出std::system_error |
thread t(rv) | Move 构造函数;创建一个新的thread object,取rv的状态并令rv变成nonjoinable |
t.~thread() | 销毁*this,如果object是joinable则调用std::terminate() |
t = rv | Move assignment;将rv的状态move assign至t,如果t是joinable则调用std::terminate() |
t.joinable() | 如果t有一个关联线程(也就是说它是joinable)便产出true |
t.join() | 等待关联线程完成工作(如果该线程不是joinable便抛出std::system_error),然后令object变成nonjoinable |
t.detach() | 解除t和线程之间的关联并且让线程继续运行(如果该线程不是joinable便抛出std::system_error),并令object变成nonjoinable |
t.get_id() | 如果joinable就返回独一无二的std::thread::id,如果不是joinable就返回std::thread::id() |
t.native_handle() | 返回一个依赖平台的类型native_handle_type,用于不具可移植性的扩展 |
除了上表中列举的接口外,thread还提供了一个static函数,用来查询并行线程的可能数量(只是一个参考值):
unsigned int std::thread::hardware_concurrency();
注意:如果线程数量不可计算或者不明确,hardware_concurrency返回值是0;
与async()相同之处
thread与async一样,可以传入任何callable object(可以是function、member function、function object、lambda),并可以夹带任何可能的实参。注意的是:除非你真的直到你在做什么,否则面对“处理目标函数所必须”的所有object都应该以by value方式传递,使得thread只使用local copy;
与async()不同之处
1.thread没有所谓的发射策略。C++标准库永远试着将目标函数启动与一个新线程中。如果无法做到就会抛出std::system_error并带着错误码resource_unavailable_try_again;
2.没有接口可以处理线程结束结果。唯一可以获取的是一个独一无二的线程ID;
3.如果发生异常,但未被捕捉于线程之内,程序会立刻中止并调用std::terminate()。如果想要将异常传播至线程外的某个context,必须使用exception_ptr;
4.在使用thread时,必须声明是否“想要等待线程结束”,即调用join();或者打算“将它从母体卸离(detach)使它运行于后台而不受任何控制”,就调用detach()。
5.如果让线程运行于后台而main()结束了,所有线程会被鲁莽而硬性地终止;
示例代码
#include <thread>
#include <chrono>
#include <random>
#include <exception>
#include <iostream>
using namespace std;
void doSomething(int num,char c)
{
try
{
default_random_engine dre(42 * c);
uniform_int_distribution<int> id(10, 1000);
for (size_t i = 0; i < num; i++)
{
this_thread::sleep_for(chrono::milliseconds(id(dre)));
cout.put(c).flush();
}
}
catch (const std::exception& e)
{
cerr << "THREAD-EXCEPTION (thead "
<< this_thread::get_id() << "): " << e.what() << endl;
}
catch (...)
{
cerr << "THREAD-EXCEPTION (thead " << this_thread::get_id() << ")" << endl;
}
}
int main()
{
try
{
thread t1(doSomething,5,'.');
cout << "- started fg thread " << t1.get_id() << endl;
for (size_t i = 0; i < 5; i++)
{
thread t(doSomething, 10, 'a' + i);
cout << "- detach started bg thread " << t.get_id() << endl;
t.detach();
}
cin.get();
cout << "- join fg thread " << t1.get_id() << endl;
t1.join();
}
catch (const std::exception& e)
{
cerr << "EXCEPTION: " << e.what() << endl;
}
}
当等待每个线程都执行完成后,按下Return键;
当程序刚开始运行,快速按下Return键,则main函数运行结束,程序会立刻终止所有后台线程;
可能会出现下图结果:
有点危险的Detached Thread
Detached Thread(卸离后的线程)很容易形成问题——如果它们使用nonloacl资源的话。
由于程序员失去了对detached thread的控制,没有轻松的办法可以得知它是否运行,以及运行多久;所以,绝对不要让一个detached thread访问任何寿命已结束的object。基于这个理由,“以by reference方式传递变量和object”给线程,总是带有风险,强烈建议使用by value的方式传递;
同理,如果程序中使用了全局变量和静态变量,那么不能确保在主线程退出时,detached thread线程没有在使用这些变量;很可能运行着的detached thread仍有可能在访问“已被销毁”或“正在析构”的全局变量和静态变量,这样也会产生不可预期的行为;
列举以下detached thread的一般性规则:
1.确保这些全局变量和静态变量在“对它们进行访问”之所有detached thread都结束之前不被销毁;
使用条件变量(condition variable),它让detached thread用来发信号说它们已结束,main函数可以销毁或者析构这些变量;
2.以调用quick_exit()的方式结束程序;
文献
1.《C++标准库》第二版;
标签:11,std,Thread,thread,object,joinable,线程,多线程,id 来源: https://blog.csdn.net/liushao1031177/article/details/116888173