C++ std::thread概念介绍
C++ 11新标准中,正式的为该语言引入了多线程概念。新标准提供了一个线程库thread,通过创建一个thread对象来管理C++程序中的多线程。
本文简单聊一下C++多线程相关的一些概念及thread的基本用法。
0. 并行执行
程序并行执行两个必要条件:
- 多处理器(multiple processors)or 多核处理器(multicore processors)
- 软件并行
软件并发执行可分为两大类:
- 多线程并发 (同一个进程的多个线程并行);
- 多进程并发 (不同进程并行);
对于多线程,主要关注的是线程间的同步措施,用于确保线程安全;
对于多进程,主要关注的是进程间的通信机制,用于进程间传递消息和数据;
由于C++ 标准中没有多进程之间通信相关的标准,这些只能依赖于特定平台的API。本文只关注多线程相关。
1. C++多线程平台
C++11之前,window和linux平台分别有各自的多线程标准。使用C++编写的多线程往往是依赖于特定平台的。
- Window平台提供用于多线程创建和管理的win32 api;
- Linux下则有POSIX多线程标准,Threads或Pthreads库提供的API可以在类Unix上运行;
在C++11新标准中,可以简单通过使用hread库,来管理多线程。thread库可以看做对不同平台多线程API的一层包装;
因此使用新标准提供的线程库编写的程序是跨平台的。
2. pthread 或 C++ 11 thread
pthreads 是linux下的C++线程库,提供了一些线程相关的操作,比较偏向于底层,对线程的操作也是比较直接和方便的;
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)
linux上对于pthread的使用需要连接pthread库(有些编辑器可能需要 -std=c++11):
g++ source.cpp -lpthread -o source.o
尽管网上对C++ 11新标准中的thread类有很多吐槽,但是作为C++第一个标准线程库,还是有一些值得肯定的地方的,比如跨平台,使用简单。
而且新标准中可以方便的使用RAII来实现lock的管理等。
如果你想深入研究一下多线程,那么pthread是一个不错的选择。如果想要跨平台或者实现一些简单的多线程场景而不过多关注细节,那么权威的标准库thread是不二之选。
总之没有好与坏之分,适合就好。可以的话可以都了解一下。本文主要介绍后者。
3. 先理论后实践
对于多线程相关的学习,先弄清楚线程相关的一些概念,是很重要的。
比如线程安全、线程同步与互斥关系、线程如何通信、与进程的关系如何等。
不然实际写多线程程序是会碰到太多的问题,例如:
- 程序死锁,无响应;
- 执行结果不符合预期;
- 多线程性能并没有很大提升;
- 理不清程序执行流程;
- 不知道怎么调试;
- 程序运行时好时坏;
光线程安全就有很多理论要了解,这些光靠调试程序,根据结果来猜测是不可行的。
关于多线程相关的概念可以参考我之前以Python为例介绍线程的博文:
4. thread 多线程实例
看一下C++11 使用标准库thread创建多线程的例子:
#include<iostream>
#include<thread>
#include<string> using namespace std; int tstart(const string& tname) {
cout << "Thread test! " << tname << endl;
return ;
} int main() {
thread t(tstart, "C++ 11 thread!");
t.join();
cout << "Main Function!" << endl;
}
多线程标准库使用一个thread的对象来管理产生的线程。该例子中线程对象t表示新建的线程。
4.1 标准库创建线程的方式
打开thread头文件,可以清楚的看到thread提供的构造函数。
- 默认构造函数 thread() noexcept;
- 接受函数及其传递参数的构造函数 template <class _Fn, class... _Args, ...> explicit thread(_Fn&& _Fx, _Args&&... _Ax)
- move构造函数 thread(thread&& _Other) noexcept;
- 拷贝构造函数 thread(const thread&) = delete;
- 拷贝赋值运算符 thread& operator=(const thread&) = delete;
其中拷贝构造函数和拷贝赋值运算符被禁用,意味着std::thread对象不能够被拷贝和赋值到别的thread对象;
默认构造函数构造一个空的thread对象,但是不表示任何线程;
接受参数的构造函数创建一个表示线程的对象,线程从传入的函数开始执行,该对象是joinable的;
move构造函数可以看做将一个thread对象对线程的控制权限转移到另一个thread对象;执行之后,传入的thread对象不表示任何线程;
int main()
{
int arg = ;
std::thread t1; // t1 is not represent a thread
std::thread t2(func1, arg + ); // pass to thread by value
std::thread t3(func2, std::ref(arg)); // pass to thread by reference
std::thread t4(std::move(t3)); // t4 is now running func2(). t3 is no longer a thread
//t1.join() Error!
t2.join();
//t3.join() Error!
t4.join();
}
多数情况下我们使用的是上面第二种创建线程的方式。下面看一下join和detach。
4.2 join && detach
对于创建的线程,一般会在其销毁前调用join和detach函数;
弄清楚这两个函数的调用时机和意义,以及调用前后线程状态的变化非常重要。
- join 会使当前线程阻塞,直到目标线程执行完毕;
- 只有处于活动状态线程才能调用join,可以通过joinable()函数检查;
- joinable() == true表示当前线程是活动线程,才可以调用join函数;
- 默认构造函数创建的对象是joinable() == false;
- join只能被调用一次,之后joinable就会变为false,表示线程执行完毕;
- 调用 ternimate()的线程必须是 joinable() == false;
- 如果线程不调用join()函数,即使执行完毕也是一个活动线程,即joinable() == true,依然可以调用join()函数;
- detach 将thread对象及其表示的线程分离;
- 调用detach表示thread对象和其表示的线程完全分离;
- 分离之后的线程是不在受约束和管制,会单独执行,直到执行完毕释放资源,可以看做是一个daemon线程;
- 分离之后thread对象不再表示任何线程;
- 分离之后joinable() == false,即使还在执行;
join实例分析:
int main() {
thread t(tstart, "C++ 11 thread!");
cout << t.joinable() << endl;
if (t.joinable()) t.join();
//t.detach(); Error
cout << t.joinable() << endl;
// t.join(); Error
cout << "Main Function!" << endl;
system("pause");
}
简单来说就是只有处于活动状态的线程才可以调用join,调用返回表示线程执行完毕,joinable() == false.
inline void thread::join()
{ // join thread
if (!joinable())
_Throw_Cpp_error(_INVALID_ARGUMENT);
const bool _Is_null = _Thr_is_null(_Thr); // Avoid Clang -Wparentheses-equality
... ...
}
将上面的t.join()换成是t.detach()会得到相同的结果.
void detach()
{ // detach thread
if (!joinable())
_Throw_Cpp_error(_INVALID_ARGUMENT);
_Thrd_detachX(_Thr);
_Thr_set_null(_Thr);
}
上面是thread文件中对detach的定义,可以看出只有joinable() == true的线程,也就是活动状态的线程才可以调用detach。
~thread() _NOEXCEPT
{ // clean up
if (joinable())
_XSTD terminate();
}
当线程既没有调用join也没有调用detach的时候,线程执行完毕joinable() == true,那么当thread对象被销毁的时候,会调用terminate()。
4.3 获取线程ID
线程ID是一个线程的标识符,C++标准中提供两种方式获取线程ID;
- thread_obj.get_id();
- std::this_thread::get_id()
有一点需要注意,就是空thread对象,也就是不表示任何线程的thread obj调用get_id返回值为0;
此外当一个线程被detach或者joinable() == false时,调用get_id的返回结果也为0。
cout << t.get_id() << ' ' << this_thread::get_id() << endl;
//t.detach();
t.join();
cout << t.get_id() << ' ' << std::this_thread::get_id() << endl;
4.4 交换thread表示的线程
除了上面介绍的detach可以分离thread对象及其所表示的线程,或者move到别的线程之外,还可以使用swap来交换两个thread对象表示的线程。
实例来看一下两个线程的交换。
int tstart(const string& tname) {
cout << "Thread test! " << tname << endl;
return ;
} int main() {
thread t1(tstart, "C++ 11 thread_1!");
thread t2(tstart, "C++ 11 thread_2!");
cout << "current thread id: " << this_thread::get_id() << endl;
cout << "before swap: "<< " thread_1 id: " << t1.get_id() << " thread_2 id: " << t2.get_id() << endl;
t1.swap(t2);
cout << "after swap: " << " thread_1 id: " << t1.get_id() << " thread_2 id: " << t2.get_id() << endl;
//t.detach();
t1.join();
t2.join();
}
结果:
Thread test! C++ thread_1!
Thread test! C++ thread_2!
current thread id:
before swap: thread_1 id: thread_2 id:
after swap: thread_1 id: thread_2 id:
下面是thread::swap函数的实现。
void swap(thread& _Other) _NOEXCEPT
{ // swap with _Other
_STD swap(_Thr, _Other._Thr);
}
可以看到交换的过程仅仅是互换了thread对象所持有的底层句柄;
关于C++ 多线程新标准thread的基本介绍就到这里了,看到这里应该有一个简单的认识了。
关于线程安全和管理等高级话题,后面有空在写文章介绍。
C++ std::thread概念介绍的更多相关文章
- C++11 并发指南------std::thread 详解
参考: https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter3-Thread/Int ...
- Cocos2dx 3.0 过渡篇(二十六)C++11多线程std::thread的简单使用(上)
昨天练车时有一MM与我交替着练,聊了几句话就多了起来,我对她说:"看到前面那俩教练没?老色鬼两枚!整天调戏女学员."她说:"还好啦,这毕竟是他们的乐趣所在,你不认为教练每 ...
- C++11多线程std::thread的简单使用
在cocos2dx 2.0时代,我们使用的是pthread库,是一套用户级线程库,被广泛地使用在跨平台应用上.但在cocos2dx 3.0中并未发现有pthread的支持文件,原来c++11中已经拥有 ...
- std::thread使用
本文将从以下三个部分介绍C++11标准中的thread类,本文主要内容为: 启动新线程 等待线程与分离线程 线程唯一标识符 1.启动线程 线程再std::threada对象创建时启动.最简单的情况下, ...
- C++11 并发指南二(std::thread 详解)
上一篇博客<C++11 并发指南一(C++11 多线程初探)>中只是提到了 std::thread 的基本用法,并给出了一个最简单的例子,本文将稍微详细地介绍 std::thread 的用 ...
- Java基础加强之并发(一)基本概念介绍
基本概念介绍 进程:它是内存中的一段独立的空间,可以负责当前应用程序的运行.当前这个进程负责调度当前程序中的所有运行细节. 线程:它是位于进程中,负责当前进程中的某个具备独立运行资格的空间. 进程是负 ...
- C++11 并发指南二(std::thread 详解)(转)
上一篇博客<C++11 并发指南一(C++11 多线程初探)>中只是提到了 std::thread 的基本用法,并给出了一个最简单的例子,本文将稍微详细地介绍 std::thread 的用 ...
- 【C/C++开发】C++11 并发指南二(std::thread 详解)
上一篇博客<C++11 并发指南一(C++11 多线程初探)>中只是提到了 std::thread 的基本用法,并给出了一个最简单的例子,本文将稍微详细地介绍 std::thread 的用 ...
- [原]C++新标准之std::thread
原 总结 C++11 thread 概览 std::thread 类定义 各个成员函数的简单介绍 例子 更多 参考资料 概览 从C++11开始提供了线程的支持,终于可以方便的编写跨平台的线程代码了. ...
随机推荐
- 用gcc/g++编译winsock程序
用gcc/g++编译winsock程序 D:\My\code>gcc -o getweb.exe getweb.c -lwin32socket 如果不加此句 -lwin32socket 编译会报 ...
- Day4 chart基本属性分析
属性设置是基于chart实例的,所以我们必须先获取一个chart画板实例,获取方式: G2.Chart.创建 Chart 的方式如下: new G2.Chart({ container: {strin ...
- java-初读 HashTable
有用的标识符 transiant 有用的属性 初始容量11 加载因子0.75 这里理解如果要经常插入大量数据可以增大加载因子 有用的方法 @Test public void testNan() { l ...
- [FJOI2015]火星商店问题(线段树分治,可持久化,Trie树)
[FJOI2015]火星商店问题 前天考了到线段树分治模板题,全场都切了,就我不会QAQ 于是切题无数的Tyher巨巨就告诉我:"你可以去看看火星商店问题,看了你就会了." 第一道 ...
- 【Spring Boot】利用 Spring Boot Admin 进行项目监控管理
利用 Spring Boot Admin 进行项目监控管理 一.Spring Boot Admin 是什么 Spring Boot Admin (SBA) 是一个社区开源项目,用于管理和监视 Spri ...
- python学习笔记(6)--面向对象学习
本节内容: 面向对象编程介绍 为什么要用面向对象进行开发? 面向对象的特性:封装.继承.多态 类.方法. 引言 你现在是一家游戏公司的开发人员,现在需要你开发一款叫做(人狗大战)的游戏,你就思 ...
- python 25 类的成员
目录 1. 类的私有成员 -- __ 1.1 类的私有静态属性 1.2 类的私有方法 1.3 对象的私有方法 2. 类的其他方法 2.1 类方法 @classmethod 2.2 静态方法 @stat ...
- 1.Sentinel源码分析—FlowRuleManager加载规则做了什么?
最近我很好奇在RPC中限流熔断降级要怎么做,hystrix已经1年多没有更新了,感觉要被遗弃的感觉,那么我就把眼光聚焦到了阿里的Sentinel,顺便学习一下阿里的源代码. 这一章我主要讲的是Flow ...
- leetcode8 String to Integer
题目描述 Implement atoi which converts a string to an integer. The function first discards as many white ...
- 自制轮子 canvas tree
这是树的效果 这个树拥有的功能有 1.小地图显示 2.小地图点击快速定位 3.点击加减显示根节点 4.基本的数显示 5.节点拖拽 6.点击头像跳转到相关页面 7.使用直角线和直线选择 8.画布自适应 ...