std::thread使用
本文将从以下三个部分介绍C++11标准中的thread类,本文主要内容为:
- 启动新线程
- 等待线程与分离线程
- 线程唯一标识符
1.启动线程
线程再std::threada对象创建时启动。最简单的情况下,任务叶会很简单,通常是无参数无返回的函数。使用C++线程库启动线程,就是构造std::thread对象。
void do_some_work();
std::thread my_thread(do_some_work);
如同大多数标准库一样,std::thread可以调用(CallAble)类型构造,如下所示:
class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
代码中提供的函数对象,会复制到新线程的存储空间中,函数对象的调用与执行都在线程的内存空间中。
可以使用Lambda创建线程的执行体,如下:
boost::thread myThread([]
{
do_something();
do_something_else();
});
启动了线程需要明确是要等待线程结束,还是让其进行自主运行。如果std::thread对象销毁之前还没有做出决定,程序就会终止(std::thread的析构函数会调用std::terminate)。
2.等待线程完成
如果需要等待线程相关的std::thread实例需要调用join().join是简单粗暴的等待线程完成或不等待,调用join的行为清理了线程相关的存储部分,这样std::thread对象将不再与已经完成的线程有任何关联。这意味着只能对一个线程使用一次join,一旦已经使用过,std::thread对象就不能再次加入了,当对其使用joinable时将返回false.
3.后台运行线程
使用detach()会让线程在后台运行,这意味着主线程不能与其产生直接交互,也就是说不会等待这个线程结束。如果分离线程,那么就不可能有std::thread对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。C++运行库保证,当线程退出时,相关资源能够正确收回,后台线程的控制和归属,C++运行库都会处理。
通常称分离线程为守护线程,即没有任何用户接口,并在后台运行的线程。这种线程的特点就是长时间运行,线程的生命周期可能会从某一个应用起始到结束,可能会在后台见识文件系统,还可能对缓存进行清理,亦或对数据结构进行优化。另一方面,分离线程只能确定线程什么时候结束,“发后即忘”的任务就使用到线程的这种方式。
std::thread t(do_background_work);
t.detach();
assert(!t.joinable());
如上代码所示,调用std::thread的成员函数detach()来分离一个线程,之后,相应的std::thread对象就与实际执行的线程无关了,并且这个线程也不可条用join()。为了从std::thread对象中分离线程(前提是有可进行分离的线程),不能对没有执行线程的std::thread对象使用detach,也是join()的使用条件,并且要用同样的方式进行检查,当std::thread对象使用t.joinable()返回的是true,就可以使用t.detach()。
4.向线程函数传递参数
向std::thread构造函数中的可调用对象,或函数传递一个参数很简单。需要注意到的是,参数默认是要拷贝到线程独立内存中,即使参数是引用的形式,也可以在新线程中进行访问,如下所示。
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");
代码中创建了一个调用f(3,"hello")的线程。注意函数f需要一个std::string对象作为第二个参数,但这里使用的是字符串的字面值,也就是char *类型。之后在线程的上下文中完成字面值向std::string对象的转化。需要注意的是,指向动态分配的内存的指针传递给线程的情况,代码如下:
void f(int i,std::string const& s);
void oops(int some_param)
{
char buffer[1024]; // 1
sprintf(buffer, "%i",some_param);
std::thread t(f,3,buffer); // 2
t.detach();
}
这种情况下,buffer是一个指针变量,指向本地变量,然后本地变量通过buffer传递到新线程中。并且函数很可能会在字面值转化为std::string对象之前崩掉,从而导致一些未定义行为,并且想要依赖隐式转换将字面值转为为函数期待的std::string对象,但因std::thread的构造函数会复制提供的变量,就只复制了没有转换成期望类型的字符串字面值。
解决方案就是在传递到std::thread构造函数之前,就将字面值转换为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();
}
不过也有成功的情况:复制一个引用。在线程更新数据结构时,会成功的传递一个引用:
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的构造函数2并不知道。构造函数无视函数期待的参数类型,并盲目的拷贝已提供的变量。当线程调用update_data_for_widget函数时,传递给函数的是data变量拷贝的引用,而非数据本身的引用。因此拷贝的数据将会在线程结束时被销毁,且3处将会接收到没有被修改的的data变量。可以使用std::ref将参数转换称引用的形式,从而可以将线程的调用转换为一下形式:
std::thread t(update_data_for_widget,w,std::ref(data));
在这之后,update_data_for_widget将会接受一个data变量的引用,而非一个data变量拷贝的引用。
可以传递一个成员函数指针作为线程函数,并提供一个合适的对象指针作为第二个参数:如下所示,
class X
{
public:
void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work,&my_x); // 1
这段代码中,新线程将my_x.do_lengthy_work作为线程函数,;my_x的地址作为指针对象提供给函数。也可以为成员函数提供参数:std::thread构造函数的第三个参数就是成员函数的第一个参数,以此类推,如下:
class X
{
public:
void do_lengthy_work(int);
};
X my_x;
int num(0);
std::thread t(&X::do_lengthy_work, &my_x, num);
5.转移线程所有权
假设要在后台写一个启动线程的函数,想通过新线程的所有权去调用这个函数,而不是等待线程结束再去调用;或完全相反的想法:创建一个线程,并在函数中转移所有权,都必须要等待线程结束。总之,新线程的所有权都需要转移。这就是std::thread引入Move的原因。如下所示代码,创建了两个执行线程,并且在std::thread(t1,t2,t3)之间转移所有权:
void some_function();
void some_other_function();
std::thread t1(some_function); // 1
std::thread t2=std::move(t1); // 2
t1=std::thread(some_other_function); // 3
std::thread t3; // 4
t3=std::move(t2); // 5
t1=std::move(t3); // 6 赋值操作将使程序崩溃
可移动的含义为当把std::thread对象执行线程所有权转移到别的std::thread对象之后,其自身并不再拥有执行线程的所有权。所以以上代码中,t2不拥有执行线程的所有权,并且试图对拥有执行线程所有权的对象t1(6所示代码)进行移动复制时,程序将会崩溃。
std::thread支持移动,就意味着线程的所有权可以在函数外进行转移,就如下面的程序一样:
std::thread f()
{
void some_function();
return std::thread(some_function);
}
std::thread g()
{
void some_other_function(int);
std::thread t(some_other_function,42);
return t;
}
6.识别线程
线程标识类型是std::thread::id,可以通过两种方式进行检索。第一种,可以通过调用std::thread对象的成员函数get_id来直接获取。如果std::thread对象没有与任何执行线程相关联,get_id将返回std::thread::type默认构造值,这个值表示没有线程。第二种,当前线程种调用std::this_thread::get_id()也可以获得线程表示。
std::thread::id对象可以自由的对比和复制,因为标识符就可以复用。如果两个对象的std::thread::id相等,那它们就是同一个线程,或都没有线程。如果不等,那么就代表了不同的线程,或者一个线程有,另一个线程没有。
Reference:
C++ Concurrency In Action Pratical Multithreading.
std::thread使用的更多相关文章
- std::thread
std::shared_ptr<std::thread> m_spThread; m_spThread.reset(new std::thread(std::bind(&GameS ...
- 用std::thread替换实现boost::thread_group
thread_group是boost库中的线程池类,内部使用的是boost::thread. 随着C++ 11标准的制定和各大编译器的新版本的推出(其实主要是VS2012的推出啦……),本着能用标准库 ...
- C++11 并发指南------std::thread 详解
参考: https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter3-Thread/Int ...
- C++ 11 笔记 (五) : std::thread
这真是一个巨大的话题.我猜记录完善绝B需要一本书的容量. 所以..我只是略有了解,等以后用的深入了再慢慢补充吧. C++写多线程真是一个痛苦的事情,当初用过C语言的CreateThread,见过boo ...
- Cocos2dx 3.0 过渡篇(二十六)C++11多线程std::thread的简单使用(上)
昨天练车时有一MM与我交替着练,聊了几句话就多了起来,我对她说:"看到前面那俩教练没?老色鬼两枚!整天调戏女学员."她说:"还好啦,这毕竟是他们的乐趣所在,你不认为教练每 ...
- C++11多线程std::thread的简单使用
在cocos2dx 2.0时代,我们使用的是pthread库,是一套用户级线程库,被广泛地使用在跨平台应用上.但在cocos2dx 3.0中并未发现有pthread的支持文件,原来c++11中已经拥有 ...
- Effective Modern C++ Item 37:确保std::thread在销毁时是unjoinable的
下面这段代码,如果调用func,按照C++的标准,程序会被终止(std::terminate) void func() { std::thread t([] { std::chrono::micros ...
- linux每日一练:Enable multithreading to use std::thread: Operation not permitted问题解决
linux每日一练:Enable multithreading to use std::thread: Operation not permitted问题解决 在linux在需要使用c++11时会遇到 ...
- Microsoft C++ 异常: std::system_error std::thread
第一次使用std::thread,把之前项目里面的Windows的thread进行了替换,程序退出的然后发生了std::system_error. 经过调试,发现std::thread ,join了两 ...
随机推荐
- KoaHub.js -- 基于 Koa.js 平台的 Node.js web 快速开发框架之koahub-body-res
koahub body res Format koa's respond json. Installation $ npm install koahub-body-res Use with koa v ...
- USACO全部月赛及GateWay数据
月赛: 以07年open为例,网站如下 http://contest.usaco.org/OPEN07 其他的格式是http://contest.usaco.org/月份(月份的英文前三位,比如1月是 ...
- elastic search 学习 一
初步阅读了elastic search 的文档,并使用command实践操作. 大概明白其概念模型.
- wp8数据存储--独立存储文件 【转】
出自 : http://www.cnblogs.com/MyBeN/p/3339019.html 文章篇幅有点大,建议去源网看看 1.调用手机的独立存储 例如:IsolatedStorageFile ...
- Maven使用说明
maven的作用. Maven 是一个项目管理和构建自动化工具.可以方便我们导入jar包. maven的安装. 要想安装maven,首先你需要安装jdk,并且配置jdk环境变量.右键点击计算机,选择属 ...
- 在docker container中运行docker-compose
为了保持宿主主机的环境干净,因此将docker-compose安装到一个基于centos7.3的容器之中,因为tianchao屏蔽了amazonaws,最后选择了通过pip方式来安装,这也是官方推荐的 ...
- 浅谈对java中传参问题的理解
之前用的c/c++比较多,在c/c++中对于传参类型,无外乎就是传值.传引用.传指针这几种.但在java中,由于没有指针类型,其传参的方式也发生了相应的变化.在网上找了找,按我之前的理解,java中传 ...
- Maven项目搭建(二):Maven搭建SSM框架
上一章给大家讲解了如何使用Maven搭建web项目. 这次给大家介绍一下怎么使用Maven搭建SSM框架项目. 首先我们来看一下pom.xml的属性介绍: project: pom的xml根元素. p ...
- Akari谜题(关灯问题)的开灯解法
提高解时预处理的速度 本方法的结果是得到满足所有黑色有数字方块的一个带有未照亮的块的可能解集. 解集大小为 4~(3号块数量+1号块数量)+6~(2号块数量)-灯互相照射到的解的集合.集合中的灯为黄色 ...
- 老李分享:走读unittest源码
老李分享:走读unittest源码 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.poptest测试开发工程师就业培训感兴趣 ...