【C++并发实战】(二)线程管理
本篇主要讲述线程的管理,主要包括创建和使用线程
启动线程
线程出现是为了执行任务,线程创建时会给一个入口函数,当这个函数返回时,该线程就会退出,最常见的main()函数就是主线程的入口函数,在main()函数返回时主线程就结束了。 如何启动一个线程呢?就如上所述,需要给线程对象一个入口函数。
#include <iostream>
#include <thread>
using namespace std;
void hello()
{
cout << "hello world";
}
int main()
{
thread t(hello);
t.join();
return 0;
}
上述代码中实现了一个简单的线程
一旦线程被启动,就必须要显式地决定是要等待线程完成还是让线程自己运行,这个决定必须要在线程对象销毁前完成,否则,程序将会被终止(抛出异常),如果不等待线程完成,则需要保证线程访问的数据必须是有效的,直到该线程终止,在这种情况下,必须要小心的使用局部变量,局部变量的指针和引用。
PS:需要注意的是等待线程完成和分离线程必须要在线程对象销毁前完成,并不是在线程函数执行结束前完成。原因是线程对象被销毁后不能直接与线程通信,将无法等待或者分离。 例如:
#include <iostream>
#include <thread>
#include <stdlib.h>
#include <windows.h>
using namespace std;
void hello()
{
cout << "hello world";
}
int main()
{
thread t(hello);
Sleep(3000);
t.join();
return 0;
}
这个代码中线程执行的hello函数已经结束,依然可以join
等待线程完成
在上面的代码例子中,使用了线程对象的join函数,这个函数的作用就是等待线程完成。同时需要注意,一个线程不能被join两次。可以通过线程对象的joinable()函数判断当前函数是否可以被join。
std::thread t(do_thread_work);
if(t.joinable())
t.join();
分离线程
除了可以等待线程完成,还可以分离线程,让线程自己运行,也就是所谓的在后台运行线程,当线程对象被销毁后,线程可能仍在运行,此时,没有直接的方法可以与其通信,不能再通过线程对象获取到该线程,也不能再被join,所以要注意资源的请求和释放。
参照守护进程的概念,被分离的线程通常被称作守护线程。
std::thread t(do_some_background_work);
t.detach();
assert(!t.joinable());
为了分离线程,线程对象必须与一个线程相关联,不能在没有线程关联的线程对象上使用detach(),join()函数也是同理,因此也可以使用joinable()函数来判断当前函数是否可以detach。
在异常环境下的等待
如上所述,必须在线程对象被销毁之前调用join()或者detach(),如果需要分离线程,一般会在线程对象创建好后立刻调用,这样问题不大。但是如果打算等待线程完成,就需要考虑在哪个位置调用join。如果在线程开始之后join执行之前发生了异常,对join的调用可能就会被跳过。所以应该确保join函数被调用,以免程序被终止。
std::thread t(my_function);
try
{
do_something();
}
catch
{
t.join();
throw();
}
t.join();
当然还有其他的做法,利用RAII的思想,类似智能指针,对thread对象进行封装。
#include <iostream>
#include <thread>
class thread_guard
{
public:
explicit thread_guard(std::thread& t)
:t_(t)
{}
~thread_guard()
{
if (t_.joinable())
{
t_.join();
}
}
private:
std::thread& t_;
};
void hello()
{
std::cout << "hello world";
}
int main()
{
std::thread t(hello);
thread_guard g(t);
return 0;
}
传递参数给线程函数
线程的入口是个函数,作为使用者肯定是想传递一些函数参数的。操作也比较简单,将额外的参数传递给std::thread的够凹函数就可以了。但是,需要重视的一点是,参数默认上会被复制到内部存储空间,然后在那新创建的线程可以访问这些参数,即便函数中的相应参数期待着引用
这个意思就是说,传递参数给县城函数就像是给普通函数传递参数一样,默认传递的是形参,而且就算你加了引用,传递的也是形参。是std::thread的构造函数做的这个事情,他无视函数所期望的引用盲目的复制了所提供的值
解决方案也很简单,使用std::ref()包裹你想要以引用传递的参数。
void update_data_for_widget(widget_id w, widget_data& data);
void fun(widget_id w)
{
widget_data data;
std::thread t(update_data_for_widget, w, std::ref(data));
}
如果不这么做,传递的data在线程函数中的改变是不会起作用的。
转移线程的所有权
和std::unique_ptr类似,std::thread是可移动并且非可复制的,这意味着一般不会有两个线程对象指向同一个线程实例,如果出现此情况会发生异常。如果同时给一个线程对象关联两个线程实例,也会触发异常。
void some_function();
void some_other_function();
std::thread t1(some_function);
std::thread t2 = std::move(t1);
t1 = std::thread(some_other_function);
t3 = std::move(t2);
t1 = std::move(t3); //此项操作将会终止程序!
标识线程
线程标识符是std::thread::id类型的,获取方式有以下两种
- 可以通过与线程相关联的std::thread对象调用get_id()方法获取。如果该线程对象没有相关联的线程,则此方法会返回一个默认构造的std::thread::id对象用来表示“没有线程”
- 另外可以通过std::this_thread::get_id()获取当前现成的id。
id可以自由地被用于复制和比较,或者是被排序。
- WordPress前端html代码压缩优化,附对应知更鸟主题压缩报错的解决方案
- 原创插件:网站收录查询和显示WordPress插件(自定义栏目优化版)
- Linux系统crontab备份数据库执行不成功?可能是百分号%在作怪!
- go语言十大排序算法总结
- BaiduSubmit:度娘WordPress结构化数据插件(改进版)
- Android自绘动画实现与优化实战——以Tencent OS录音机波形动画为实例
- Go语言归并排序算法实现
- grep无法查找shell传过来的变量?先注意一下文本格式吧!
- 深入浅出 Retrofit,这么牛逼的框架你们还不来看看?
- nwui —— 又一个go语言图形界面解决方案
- Golang 通用连接池
- 解决JS操作Cookies出现的乱码问题,修复WordPress评论乱码
- 分享一个WordPress外链跳转教程,兼容知更鸟暗箱下载和文章索引
- 让WordPress RSS/Feed订阅数据延迟发布,附RSS技巧集锦
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Stream API
- Lambda表达式
- ES的Query、Filter、Metric、Bucketing使用详解
- Golang的单引号、双引号与反引号
- CentOS配置docker和docker-compose
- 给Linux增加swap内存
- 网鼎杯2018-Fakebook
- 强网杯2019-高明的黑客
- CISCN2019华北赛区Day2-HackWorld
- ZJCTF-NiZhuanSiWei
- xxe漏洞学习
- De1CTF2019-SSRFME
- BJDCTF2nd-EasyMd5
- BJDCTF2nd-fakegoogle
- java_Scanner类、Random类、ArrayList 类的使用