当前课程知识点:基于Linux的C++ > 第十四讲 线程编程 > 14.9 C++11线程库(一) > LinuxCPP1409
接下来一节是C++11线程库
C++11为我们提供了丰富的线程功能
也就是多线程编程的能力
在C++11以后 语言本身就提供了
它的最主要的好处
就能够支持我们平台无关的并行程序开发
它提供了五个主要的库
一个是atomic 一个是thread
一个是mutex一个是condition_variable
最后一个是future
首先是thread
这是我们的核心库 thread
它是一个线程类
提供了一个线程类——std::thread
这样的一个类
然后内部还定义了一个名空间
叫std::this_thread
这样的一个名空间
用它存了一些常用的线程管理函数
就在那个名空间里
定义了一系列的常用的线程管理函数
第二个就是mutex这个库
这个库就是互斥相关类
都在这个mutex这个库里边
包括标准的mutex这个互斥类
包括特定的互斥类
包括管理这些互斥类的几个管理类
最主要的是lock_guard
还有unique_lock
第三个是condition_variable
这是条件变量
相关的条件变量类 都放在这里
atomic是原子类
主要定义的是atomic这样的一个类
实际上是一个模板
然后是atomic_flag这样的一个类
另外还有一套C风格的
原子型和原子操作函数
那是早期的遗留
所以实际上这里面的实现
稍微有那么一点点小混乱
最后是一个future库
future库包括两个承诺类
一个是std::promise
还有一个td::packaged_task
打包任务
这两个都是承诺
相对应地就有一个期许
std::future
还有std::shared_future
这是一个共享期许
那个是标准期许
这是两个期许
你想给我一个承诺
这边就有一个期望
这是一个期许
要么怎么叫许诺呢 就这个意思
一个叫期许 一个叫承诺
我们首先来看线程类
线程类的名字就叫thread
就定义在thread这个库里
它支持的线程函数
没有参数或返回值的特别要求
就是说线程函数有没有参数都可以
有没有返回值呢都可以
要比Linux标准的那个线程函数
功能一下子就扩展了很多
那个函数必须带一个哑型指针
作为函数参数
返回值必须是哑型指针
这里都不做要求
什么函数都可以
这样的话就极大地方便了我们的编程
同时 与Linux线程机制相比
C++11这个线程类的实现
其实会简单一点
因为要支持跨平台
所以很多功能在Linux上面有
换了别的操作系统可能就没有
所以为了支持跨平台
它实现会简单一点
同时它使用上其实也更方便
线程局部存储直接使用thread_local
这个关键字定义它就完了
使用起来要比Linux那个实现方便很多
重要地 你可以派生自己的thread的类
你如果觉得标准库里面
这个thread这个类不够你用
它不满足Linux编程的要求
那你可以在此基础之上
派生自己的线程类
但是你要特别注意
在实现自己的线程类的时候
这个线程类只能支持移动语义
不能支持拷贝语义
它是不允许拷贝的
那个线程的管理是操作系统来去处理的
所以它是不支持拷贝语义的
只能移动 不能拷贝
你在实现自己线程类的时候
要特别注意这一点
常用的线程类的成员函数有这么三个
一个是看这个线程是不是处于可联状态
就是joinable() 看一下
等待这个线程结束呢 就join()
想分离这个线程呢 就detach()
就这三个函数
和Linux那三个函数是对应的
定义于名空间的这几个线程管理函数
我们经常会用到的
一个是getid() 获取线程的ID
还有一个在处于等待状态的时候
让调度器选择其它线程去调度去吧
你别管我了 就这个意思
调用yield()
还有一个阻塞当前的线程
指定的时长
用sleep_for()这样的一个函数
还有一个阻塞当前的线程
到指定的时点
你可以用下面的这个函数sleep_until()
当然这两个函数带的都是一些模板参数
时间设定的模板参数
有的是时间 有的是期限
它的设定模板参数用起来比较复杂
那几个东西都在chrono那个名空间里
都在chrono里
所以你要去看chrono那个定义
才能够去使用它
我们来看几个例子
一个是一个无参数的线程函数该怎么用
我们定义了一个线程函数ThreadFunc()
用的时候特简单
std::thread 那是那个类的名字
t 定义这个类的对象
小括号对 构造它
传什么呢 传线程函数&ThreadFunc
写不写“&”都是取它的函数入口地址
OK 搞定了我们就创建了一个线程
这个线程创建完了以后
操作系统立即就会调度它
自动调度 自动执行
线程做完了 t.join() 等它
你看我们创建了一个线程
就这么简单的两条语句
一个线程对象的定义
一个线程对象的成员函数调用
这事就做完了 简单吧
什么细节你都不用管
当然特别方便
这是无参数的线程函数
如果这个线程是需要带参数怎么办
你比如我们这里这个线程函数
ThreadFunc()将会带双参数
两个整数a和b
我要把这两个整数加在一块
简单 在定义这个线程对象的时候
传附加参数
线程对象的构造函数的参数是可变的
第一个是线程函数的入口地址
后边紧跟着的
就是你要传给那个线程函数的参数
想传几个就传几个
想传什么型 就传什么型
写在后边它就给你照抄过去
这个线程的构造函数就中转一下
就直接把它扔给了线程函数
很典型吧 想传俩参数
就在构造这个线程对象的时候
作为附加参数传进去 over
然后t.join() 等待线程结束 做完了
这就是带双参数的线程函数
就比刚才多俩参数 其它没变化
第三个 我们可以使用
带双参数的函子对象
作为构造线程时传进去的那个参数
这就比Linux线程编程灵活多了
Linux里边只能传线程函数啊
你可不能传函子对象
到了我们C++11里你可以传函子对象
没问题
你可以定义一个函子Functor
在这里边你重载了函数调用操作符
OK 没有问题
这个函数调用操作符有没有参数
随意 随便你 看你程序需要
有参数就写 没参数就不用写
然后你构造这个线程对象的时候
第一个参数就传那个Functor()
我们构造这个函子的一个匿名对象传进去
传一个函子进去 OK
它就会调用那个函子重载的
函数调用操作符
把它当做一个函数调用
然后你在构造这个线程对象的时候
传的额外这两个参数
就会作为那个线程函数的参数传进去
特方便吧 这是第三个例子
第四个例子
你还可以使用标准库里的std::bind()这个函数
绑定一个对象和它的普通的成员函数
作为我们的线程函数
这就很神奇了 你比如讲
我们这里面定义一个类class Worker
这是我们的劳工类
它专门做我们的线程执行的
里面有一个成员函数叫ThreadFunc()
具体的代码我就不写了
跟刚才是一模一样的
我们构造一个线程对象t
小括号对里面传一个参数
那个参数就是std::bind()
这个函数的返回值
它给你绑定好了以后的
那个类的成员函数
std::bind()这个函数本身
我们这里传两个参数
第一个参数就是
那个类的成员函数的入口地址
所以我们传&Worker::ThreadFunc
第二个参数你要传那个对象
所以我们传worker这个对象的基地址 &worker
把这个对象的基地址传过去
那么它就会在这个对象上
调用这个普通的成员函数
就用它作为我们的线程函数
这是Linux线程库里做不到的事情
很神奇吧
就可以做完
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口