《C++并发编程实战》读书笔记(1):线程管控
1、线程的基本管控
包含头文件<thread>后,通过构建std::thread对象启动线程,任何可调用类型都适用于std::thread。
void do_some_work();
struct BackgroundTask
{
void operator()() const;
};
//空的thread对象,不接管任何线程函数
std::thread t1;
//传入普通函数
std::thread t2(do_some_work);
//传入lambda函数
std::thread t3([]() { /*do something*/ });
//传入可调用对象
BackgroundTask task;
std::thread t4(task);
//不能使用std::thread t4(BackgroundTask()),虽然本意是传入临时变量,但这会被编译器解释成函数声明。多用一对圆括号或者使用列表初始化可以解决这个问题。
std::thread t5((BackgroundTask()));
std::thread t6{BackgroundTask()};
启动线程后,需要明确是等待它结束、还是任由它独自运行:
- 调用成员函数
join()会先等待线程结束,然后隶属于该线程的任何存储空间都会被清除,std::thread对象不再关联到已结束的线程。 - 调用成员函数
detach()会分离线程使其在后台运行,此后无法获得与它关联的std::thread对象。分离线程的归属权和控制权都转移给了C++运行时库,线程退出时与之关联的资源会被正确回收。
调用了join()或是detach()之后,其joinable()方法将返回false,所以也就不能再调用join()。不能对空的std::thread对象调用join()或是detach()。如果线程启动后既不调用join()也不调用detach(),那么当std::thread对象销毁时,其析构函数将调用std::terminate()终止整个程序。
2、向线程函数传递参数
若需向线程上的函数传递参数,直接向std::thread的构造函数添加更多参数即可。线程具有内部存储空间,参数会按照默认方式先复制到该处,然后这些副本被当作临时变量,以右值形式传递给线程上的函数。即使函数的相关参数按设想应该是引用,上述过程依然会发生。
void f(int i, const std::string& s);
std::thread t(f, 3, "hello");
上述代码在新线程上调用f(3, "hello"),尽管f()的第二个参数是std::string类型,但字符串内容仍然以指针const char*的形式传入,直到进入新线程的上下文环境后才转换为std::string类型。所以如果参数是指针,需要特别注意其生命周期,否则可能导致严重问题,例如:
void f(int i, const std::string& s);
void oops(int param)
{
char buffer[1024];
sprintf(buffer, "%d", param);
std::thread t(f, 3, buffer);
t.detach();
}
buffer是指向局部数组的指针,我们原本设想buffer会在新线程内转换成std::string对象,但在此完成之前,oops()函数很有可能已经退出,导致局部数组被销毁从而引发未定义的行为。这一问题的根源在于:std::thread的构造函数原样复制所提供的值,并未立即将其转换成预期的参数类型,等到转换发生时,指针可能已经失效。所以解决方法就是,在buffer传入std::thread的构造函数之前,就先把它转换成std::string对象:
std::thread t(f, 3, std::string(buffer));
除了指针外,传递引用也需要小心。例如我们想要的是非const引用:
void update_widget_data(WidgetData& data);
void oops()
{
WidgetData data;
std::thread t(update_widget_data, data);
t.join();
}
根据update_widget_data函数的声明,参数需要以引用的方式传入,但std::thread的构造函数对此却毫不知情,它忽略了函数所期望的参数类型,直接复制了我们提供的值。然而,线程库的内部代码会把参数的副本(std::thread构造时由对象data复制得出,位于新线程的内部存储空间)以右值的形式传递给update_widget_data函数,所以这段代码会编译失败,因为不能向非const引用传递右值。解决方法就是使用std::ref()函数加以包装,这样传递给update_widget_data函数的就是指向data的引用,代码就能编译成功:
std::thread t(update_widget_data, std::ref(data));
要将某个类的成员函数设为线程函数,我们需要给出合适的对象指针作为第一个参数,成员函数的参数放在其后的位置。
class X
{
public:
void do_lengthy_work();
}
X my_x;
std:thread t(&X::do_lengthy_work, &my_x);
对于只能移动、不能复制的对象,传递参数时需要使用std::move()来转移归属权。在下面的例子中,BigObject对象的归属权会发生转移,先进入新创建的线程的内部存储空间,再转移给process_big_object()函数。
void process_big_object(std::unique_ptr<BigObject>);
std::unique_ptr<BigObject> p(new BigObject);
std::thread t(process_big_object, std::move(p));
3、移交线程归属权
std::thread不能复制,但支持移动语义。对于一个具体的执行线程,其归属权可以在多个std::thread实例之间转移。
void some_function();
void some_other_function();
std::thread t1(some_function);
std::thread t2 = std::move(t1); //将线程的归属权显式地转移给t2
t1 = std::thread(some_other_function); //线程原本与std::thread临时对象关联,其归属权随即转移给t1
std::thread t3; //按默认方式构造,未关联任何线程
t3 = std::move(t2); //t2原本关联的线程的归属权转移给t3
//经过上面这些转移,t1与运行some_other_function的线程关联,t2没有关联线程,t3与运行some_function的线程关联
t1 = std::move(t3); //在这次转移之时,t1已经关联运行some_other_function的线程,因此std::thread的析构函数中会调用std::terminate(),终止整个程序。所以只要std::thread对象还在管控着一个线程,就不能简单地向它赋新值。
因为std::thread支持移动语义,所以只要容器同样知悉移动意图,就可以装载std::thread对象。因此我们可以写出下列代码,生成多个线程,然后等待它们运行完成。
void do_work(unsigned id);
void foo()
{
std::vector<std::thread> threads;
for (unsigned i = 0; i < 20; ++i)
{
threads.push_back(std::thread(do_work, i));
}
for (auto& entry : threads)
{
entry.join();
}
}
4、识别线程
使用C++标准库的std::thread::hardware_concurrency()函数可以获取系统中逻辑处理器的数量。如果信息无法获取,该函数可能返回0。
线程ID的类型是std::thread::id,它有两种获取方法:
- 在与线程关联的
std::thread对象上调用成员函数get_id(),即可得到该线程的ID。如果std::thread对象没有关联任何执行线程,则调用get_id()返回的是按默认构造方式生成的std::thread::id对象,表示“线程不存在”。 - 当前线程的ID可以通过调用
std::this_thread::get_id()获取。
std::thread::id对象可以支持很多种操作:
- 可随意进行复制操作或比较运算。
- 可用作关联容器(
std::set、std::map、std::multiset、std::multimap)的键值,无序关联容器(std::unordered_set、std::unordered_map、std::unordered_multiset、std::unordered_multimap)的键值,或用于排序。 - 写到输出流,例如
std::cout << std::this_thread::get_id();。
《C++并发编程实战》读书笔记(1):线程管控的更多相关文章
- Java并发编程实战 读书笔记(一)
最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一 线程与进程 进程与线程的解释 个人觉 ...
- Java并发编程实战 读书笔记(二)
关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...
- 《java并发编程实战》笔记
<java并发编程实战>这本书配合并发编程网中的并发系列文章一起看,效果会好很多. 并发系列的文章链接为: Java并发性和多线程介绍目录 建议: <java并发编程实战>第 ...
- 读书笔记-----Java并发编程实战(一)线程安全性
线程安全类:在线程安全类中封装了必要的同步机制,客户端无须进一步采取同步措施 示例:一个无状态的Servlet @ThreadSafe public class StatelessFactorizer ...
- Java并发编程实践读书笔记(5) 线程池的使用
Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...
- Java并发编程实战 第8章 线程池的使用
合理的控制线程池的大小: 下面内容来自网络.不过跟作者说的一致.不想自己敲了.留个记录. 要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析: 任务的性质:CPU密集型任务.IO ...
- Java多线程编程实战读书笔记(一)
多线程的基础概念本人在学习多线程的时候发现一本书——java多线程编程实战指南.整理了一下书中的概念制作成了思维导图的形式.按照书中的章节整理,并添加一些个人的理解.
- 《Java并发编程实战》第二章 线程安全性 读书笔记
一.什么是线程安全性 编写线程安全的代码 核心在于要对状态訪问操作进行管理. 共享,可变的状态的訪问 - 前者表示多个线程訪问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与 ...
- 《Java并发编程实战》第八章 线程池的使用 读书笔记
一.在任务与运行策略之间的隐性解耦 有些类型的任务须要明白地指定运行策略,包含: . 依赖性任务.依赖关系对运行策略造成约束.须要注意活跃性问题. 要求线程池足够大,确保任务都能放入. . 使用线程封 ...
- Java并发编程实践读书笔记(1)线程安全性和对象的共享
2.线程的安全性 2.1什么是线程安全 在多个线程访问的时候,程序还能"正确",那就是线程安全的. 无状态(可以理解为没有字段的类)的对象一定是线程安全的. 2.2 原子性 典型的 ...
随机推荐
- PL/SQL查询中文乱码问题
使用PL/SQL查询数据时,有中文的内容显示乱码,如下所示: 解决方案:配置环境变量 新建系统变量: 变量名:NLS_LANG 变量值:AMERICAN_AMERICA.ZHS16GBK 然后重启PL ...
- Nuxt.js 应用中的 components:dirs 事件钩子详解
title: Nuxt.js 应用中的 components:dirs 事件钩子详解 date: 2024/10/31 updated: 2024/10/31 author: cmdragon exc ...
- 对背包dp的再探究:以 采药 和 疯狂的采药 为例
题目链接: 01背包:采药 完全背包:疯狂的采药 关于 "总体积刚好是 V " 和 "总体积 \(\le\) V " 正常的背包推导的都是 "总体积刚 ...
- 计算机网络基础/进制转换/企业级子网IP划分
数制的含义 数制:计数的方法,指用一组固定的符号和统一的规则来表示数值的方法 数位:指数字符号在一个数中所处的位置 基数:指在某种进位计数制中,数位上所能使用的数字符号的个数 位权:指在某种进位计数制 ...
- Navicat Premium 16下载与安装
1.可以通过以下两种方式下载 a.官网下载地址 https://www.navicat.com.cn/download/navicat-premium b.百度网盘下载地址 链接:https://pa ...
- 解决IDEA中xxxMapper.xml文件表名,字段爆红问题
我们在编写xxxMapper.xml中sql代码的时候有时会出现表名不会提示,表名爆红等情况,这个一般是没有设置IDEA的sql解析范围,下面是我遇到问题时候的解决办法 1.打开IDEA设置 2.选择 ...
- 【一步步开发AI运动小程序】十二、自定义一个运动分析器,实现计时计数01
随着人工智能技术的不断发展,阿里体育等IT大厂,推出的"乐动力"."天天跳绳"AI运动APP,让云上运动会.线上运动会.健身打卡.AI体育指导等概念空前火热.那 ...
- 鸿蒙NEXT开发案例:二维码的生成与识别
[引言] 在本篇文章中,我们将探讨如何在鸿蒙NEXT平台上实现二维码的生成与识别功能.通过使用ArkUI组件库和相关的媒体库,我们将创建一个简单的应用程序,用户可以生成二维码并扫描识别. [环境准备] ...
- Django admin 结合富文本编辑器tinymce
后台需要加入富文本编辑器于是选择tinymce 官方网站:http://django-tinymce.readthedocs.org/ django-tinymce · PyPI GitHub:Git ...
- 记一次 .NET某hdp智能柜系统 卡死分析
一:背景 1. 讲故事 停了一个月时间没有更新博客了,主要是这段时间有些许事情导致心神不宁,我这个人也比较浮躁所以无法潜心修炼,事情如下: 被狗咬了 也不知道是不是出门没看黄历,在小区门口店里买烟,被 ...