Effective Modern C++ Item 37:确保std::thread在销毁时是unjoinable的
下面这段代码,如果调用func,按照C++的标准,程序会被终止(std::terminate)
void func()
{
std::thread t([]
{
std::chrono::microseconds dua();
std::this_thread::sleep_for(dua);
});
}
原因在于C++标准规定,std::thread的析构被调用时,std::thread必须是unjoinable的,否则std::terminate就会被调用。
std::thread有两种状态,joinable和unjoinable,unjoinable的std::thread包括:
- 使用默认构造的std::thread。这种std::thread没有任何执行任务。
- 被移动的std::thread。比如std::thread t2(std::move(t1)),这时t1的执行工作就转移给了t2,t1变成了unjoinable的状态。
- 已经被join的std::thread。调用了join之后,std::thread就变成了unjoinable的状态。
- 已经被detach的std::thread。detach会断开std::thread和执行任务之间的连接。
之前的func中创建的thread,在销毁时是属于joinable状态的(不是默认构造,没有被移动,没有join和detach,并且线程还在运行),所以按照C++的标准,程序会被强行终止。
为什么C++要采用这种暴力的方式?因为如果采用别的方式,都会导致相应的问题。
我们假设C++标准采用其他的方式,分别分析会有什么问题:
1. 在std::thread的析构里显式调用join。这种方式会导致潜在的性能问题,因为join是阻塞调用,那么意味着thread的析构就可能会阻塞,某些情况下并不希望thread join,而是满足一定的条件才join,比如下面这种代码:
void doSomething()
{
std::thread t(doWork());
if(someCondition())
{
t.join();
getResult();
}
}
代码的本意是在condition满足时才会join线程,不满足就直接返回。因为std::thread的析构里会显式join,那么即使condition不满足,在函数退出时也会join。如果doWork是耗时的步骤,那么不管condition满不满足,doSomething都会阻塞直到doWork完成。
2. 在std::thread的析构里显式调用detach。这种方式看上去不会有第一种方式的性能问题,其实更糟糕,可能会导致runtime error。比如下面这种代码:
void doSomething()
{
std::vector<int> data;
std::thread t([&data]
{
for (int i = ; i <= ; ++i)
data.push_back(i);
});
}
当函数退出时,std::thread调用detach,那么线程的执行任务还在继续,函数栈的临时变量已被销毁,程序就会出现undefined行为,而且调试起来也很困难。detach本身就容易导致bug,所以这种方式是无法使用的。
由于上面的2个方式都有问题,所以C++采用了暴力终止程序的方式,实际上C++的这种做法强迫程序员必须保证std::thread销毁时有正确的行为,否则,你的程序就会被干掉。这是C++的哲学,其他语言对于这个问题并不一定使用这种方式。
Meyers的建议是“Make std::threads unjoinable on all paths”,也就是让std::thread在销毁时是unjoinable的。这是一种trade-off, 和之前的第一种做法一样会导致潜在的性能问题。但是相比于其他两种选择:程序被终止;detach的undefined行为,这是可以接受的(对于性能问题,可以通过实现interruptible threads来弥补)。
为了确保“Make std::threads unjoinable on all paths”,那么在函数返回和异常发生时,thread要是unjoinable状态的,所以可以用RAII来完成:
class ThreadRAII
{
public:
enum class DtorAction { join, detach };
public:
ThreadRAII(std::thread&& t, DtorAction act)
:m_action(act), m_thread(std::move(t)) {} ~ThreadRAII()
{
if (m_thread.joinable())
{
if (m_action == DtorAction::join)
m_thread.join();
else
m_thread.detach();
}
} ThreadRAII(ThreadRAII&&) = default;
ThreadRAII& operator=(ThreadRAII&&) = default; std::thread& get() { return m_thread; } private:
std::thread m_thread;
DtorAction m_action;
};
ThreadRAII构造函数接收std::thread rvalue,因为std::thread不可复制,调用move之后,传进来的std::thread就变成了unjoinable的,执行任务就转移给了ThreadRAII的std::thread。
有了ThreadRAII,就可以安全地使用std::thread:
void doSomething()
{
ThreadRAII t(std::thread([]
{
....
}
));
if(someCondition())
{
t.get().join();
...
}
}
Effective Modern C++ Item 37:确保std::thread在销毁时是unjoinable的的更多相关文章
- [Effective Modern C++] Item 7. Distinguish between () and {} when creating objects - 辨别使用()与{}创建对象的差别
条款7 辨别使用()与{}创建对象的差别 基础知识 目前已知有如下的初始化方式: ); ; }; }; // the same as above 在以“=”初始化的过程中没有调用赋值运算,如下例所示: ...
- [Effective Modern C++] Item 6. Use the explicitly typed initializer idiom when auto deduces undesired types - 当推断意外类型时使用显式的类型初始化语句
条款6 当推断意外类型时使用显式的类型初始化语句 基础知识 当使用std::vector<bool>的时候,类型推断会出现问题: std::vector<bool> featu ...
- [Effective Modern C++] Item 5. Prefer auto to explicit type declarations - 相对显式类型声明,更倾向使用auto
条款5 相对显式类型声明,更倾向使用auto 基础知识 auto能大大方便变量的定义,可以表示仅由编译器知道的类型. template<typename It> void dwim(It ...
- [Effective Modern C++] Item 4. Know how to view deduced types - 知道如何看待推断出的类型
条款四 知道如何看待推断出的类型 基础知识 有三种方式可以知道类型推断的结果: IDE编辑器 编译器诊断 运行时输出 使用typeid()以及std::type_info::name可以获取变量的类型 ...
- [Effective Modern C++] Item 3. Understand decltype - 了解decltype
条款三 了解decltype 基础知识 提供一个变量或者表达式,decltype会返回其类型,但是返回的内容会使人感到奇怪. 以下是一些简单的推断类型: ; // decltype(i) -> ...
- [Effective Modern C++] Item 2. Understand auto type deduction - 了解auto类型推断
条款二 了解auto类型推断 基础知识 除了一处例外,auto的类型推断与template一样.存在一个直接的从template类型推断到auto类型推断的映射 三类情况下的推断如下所示: // ca ...
- [Effective Modern C++] Item 1. Understand template type deduction - 了解模板类型推断
条款一 了解模板类型推断 基本情况 首先定义函数模板和函数调用的形式如下,在编译期间,编译器推断T和ParamType的类型,两者基本不相同,因为ParamType常常包含const.引用等修饰符 t ...
- Effective Modern C++ Item 27:重载universal references
假设有一个接收universal references的模板函数foo,定义如下: template<typename T> void foo(T&& t) { cout ...
- 第25课 std::thread对象的析构
一. 线程的等待与分离 (一)join和detach函数 1. 线程等待:join() (1)等待子线程结束,调用线程处于阻塞模式. (2)join()执行完成之后,底层线程id被设置为0,即join ...
随机推荐
- 走进React
走进React React是一个构建用户界面的JavaScript库,是Facebook公司在2013年5月在github上开源的.其特点如下: 高效--React通过对DOM的模拟,最大程度地减少和 ...
- angular表格分页
<!doctype html> <html lang="en" > <head> <meta charset="UTF-8&qu ...
- 我的Java笔记
第一章 一 计算机程序:一系列有序的指令集合. 二 java语言分为三个领域 javaSE java技术核心(桌面应用程序)qq 百度云 javaEE 企业版(面向internet的应用程序)京东 淘 ...
- 浅谈Django的Q查询以及AngularJS的Datatables分页插件
使用Q查询,首先要导入Q模块: from django.db.models import Q 可以组合使用&,|操作符用于多个Q的对象,产生一个新的Q对象,Q对象也可以用~操作符放在前面表示否 ...
- ArcGIS制图技巧系列(3)—让地图更有立体感
ArcGIS制图技巧系列(3)-让地图更有立体感 by 李远祥 在前面的章节中,我们已经介绍过各种的地图效果,如发光效果,山体阴影效果,植被填充效果等,所有的这些效果不外乎是各种技术的叠加和技巧的使用 ...
- TPS及计算方法
个事务,TPS为6 / 60s = 0.10 TPS.同时我们会知道事务的响应时间(或节拍),以此例,60秒完成6个事务也同时代表每个事务的响应时间或节拍为10秒. 利特尔法则 (Little' ...
- 深入理解javaScript的深复制和浅复制
javascript有五种基本数据类型(也就是简单数据类型),它们分别是:Undefined,Null,Boolean,Number和String.还含有一种复杂数据类型,就是对象 注意Undefin ...
- SQL Server-聚焦深入理解动态SQL查询(三十二)
前言 之前有园友一直关注着我快点出SQL Server性能优化系列,博主我也对性能优化系列也有点小期待,本来打算利用周末写死锁以及避免死锁系列的接着进入SQL Server优化系列,但是在工作中长时间 ...
- mysql优化---订单查询优化:异步分页处理
订单分页查询: 老的代码是顺序执行查询数据和计算总记录数,但是如果条件复杂的话(比如关联子表)查询的时间要超过20s种 public static PagedList<Map<String ...
- HTTP协议详解【转】
当今web程序的开发技术真是百家争鸣,ASP.NET, PHP, JSP,Perl, AJAX 等等. 无论Web技术在未来如何发展,理解Web程序之间通信的基本协议相当重要, 因为它让我们理解了We ...