当前课程知识点:基于Linux的C++ >  第十四讲 线程编程 >  14.10 C++11线程库(二) >  LinuxCPP1410

返回《基于Linux的C++》慕课在线视频课程列表

LinuxCPP1410在线视频

LinuxCPP1410

下一节:LinuxCPP1411

返回《基于Linux的C++》慕课在线视频列表

LinuxCPP1410课程教案、知识点、字幕

当你要进行线程间同步的时候

你可以定义互斥类

这里面有三个主要的类

一个是mutex

一个是recursive_mutex

一个是timed_mutex

第一种是基本的互斥

它提供了三个核心的成员函数

lock()、try_lock()、unlock()

这个和我们刚才解释是一样的

这里我们就不特别说明了

这三个函数也没有什么原型可介绍的

没有参数 也没有返回值 很简单

递归互斥和定时互斥

和我们刚才的解释也是一样的

这个递归互斥

它就是允许你多次去加锁和解锁

可以避免死锁的问题

定时互斥 它有一个特定时间限制

在特定时间内要定时的

后面两个类同样有这样的成员函数

lock()、try_lock()和unlock()

还有一个叫共享定时互斥

叫shared_timed_mutex

这个只在C++14以后才有

C++11里是没有的

特别注意这一点

我们看具体的实际代码

你可以定义一个互斥类的一个对象x

在我们的线程函数里边

当需要进行同步访问的时候

要锁定这个临界区的时候

简单 在x上面调用x.lock() 锁定它

如果锁定不了 它就会阻塞

它就进不去这个临界区

锁定了它就会进入这个临界区

其它线程就进入不了了

锁嘛 是唯一的嘛

相互独占嘛

当利用完了你就x.unlock() 解锁

它就离开了这个临界区

很方便吧 就是一个缺省构造

x:std::mutexx 缺省构造

用的时候x.lock()

不用的时候x.unlock() 搞定

这就是互斥类的最基本的用法

当然还是我们前面说的

它就是有可能导致死锁的

主程序里边没有什么可说的

我们定义了一个向量

构造了8个线程

这个向量里面存的都是那个线程的地址

不是那个线程本身

因为你要是线程本身的话

这个地方是不能够简单赋值的

如果你要赋值的话那不就拷贝赋值嘛

那是不允许的

你必须是移动赋值才可以

特别注意这一条 其它没什么

就是我刚才讲的

你互斥最主要的问题

是容易导致死锁的呀对吧

如果一个特定的线程在临界区操作

导致了异常

这个锁就没有办法释放嘛

然后其它线程

想获取这个锁的线程

就被永久地阻塞了

这是第一种情况

临界区的代码里面有多路分支

那么有一些分支里边你忘了解锁

那完蛋了 它肯定也不成 对吧

有一些分支提前结束了

没有实行解锁的行为

那么这个系统肯定也有问题

想进入临界区的其它的线程

肯定也会被永久阻塞

如果多个线程同时申请这个资源的时候

加锁的次数 不一样

也可能到这个问题

两个线程同时想访问两个资源

一个拿了A 一个拿到了B

第一个线程需要第二个线程的资源

第二个线程需要第一个线程的资源

结果俩人就在那个地方僵持着了

那不就死锁了嘛

这种情况也是需要特别注意的

所以正常情况下边

你编程序的时候要特别注意这一点

就是当多个线程

同时需要多个资源的时候

那个加锁的次序一般来讲

应该保持它的一致性

如果不能够保持这一点

就需要特别注意死锁的问题

那么我们怎么避免这个死锁呢

在C++11里边有一个最基本的设计原则

所谓资源获取即初始化

所谓的RAII

叫资源获取就是初始化

就这个意思

所以在实现它的时候

我们就可以使用互斥对象管理类模板

来管理我们这样的资源

就是我们不直接使用这个互斥

那个互斥对象我们当然要定义

但是我们不直接使用那个互斥对象

我们使用互斥对象管理类的模板

构造那样的对象

来管理我们的互斥对象

讲起来很拗口

待会看例子你就知道了

在C++11里边提供了几个

锁的管理类模板

一个是lock_guard

这是一个简单的

基于作用域的锁管理类模板

构造这个lock_guard的时候

它对应的那个互斥锁是不是加锁的

是可选的 你可以选择

如果你不加锁

它就假定当前线程

已经获得了这个锁的所有权

随时都可以把它加锁

析构的时候呢它是自动解锁的

你只管lock就OK了

你不用管unlock

实际上如果你构造的时候就加锁

你连lock都不用管

它自动就帮你锁上

为什么叫基于作用域的锁管理类模板呢

就是你定义了这个对象

在它离开它的作用域以后

这个对象就会被释放

当这个对象被释放的时候

那个锁自动地就会解开

所以解锁这个动作

大部分的时候你就不用关心了

这是基于作用域的

锁管理类模板 lock_guard

第二种就是独一锁的管理类模板

叫unique_lock

构造的时候是不是加锁依然是可选的

对象析构的时候

如果持有这个锁

它就会自动地解锁

但是它有一个特别的地方就是

它为什么叫独一锁呢

就是这个所有权可以转移

对象的生存期里

允许你多次的手工地调用

加锁和解锁的这个动作

你可以去做这个事情

lock_guard你不用管

lock或unlock那个事

它自动替你处理这个问题

大部分时候是这样

这个unique_lock呢

你可以手工地去决定什么时候加锁

什么时候解锁

还有一个叫共享锁的管理类模板

那是C++14以后才有的叫shared_lock

有三个互斥管理策略

第一个std::defer_lock

它就是在构造我们互斥管理对象的时候

延迟这个加锁操作

不立即把它锁定

第二个是try_to_lock

尝试着去锁定

锁定不成功的时候

它就不阻塞这个线程

互斥体如果不可用

它就直接就返回了 不会锁定的

第三个就是adopt_lock

它什么意思呢

就是假定当前这个线程

已经获得了这个互斥体的所有权

它不再加锁

它不尝试加锁这个动作

就是假定这个锁你已经拿到了

加锁那是后来的事

在使用这个互斥管理策略的时候要注意

如果你没有提供它的互斥管理策略

那么缺省的情况下

它在构造这个互斥管理对象的时候

就会阻塞当前的线程

直到成功地获取对应的互斥

有一点是需要特别注意的

这个互斥什么时候解锁是恰当的

虽然C++11

提供了这个互斥自动管理策略

看上去很方便

让我们在编程的时候不用太关心

什么时候加锁

什么时候解锁这个问题

但实际上事情没那么简单

如果你在一个作用域内

定义了一个互斥管理对象

加锁了

临界区完了

但是作用域还没有结束

那个互斥就没有被解锁呀

其它的线程都会被阻塞了

所以特别注意这一条

所以在这种情况下边

临界区的代码本身就不应该太长

可是如果你忘了

在这个临界区代码结束之后

立即把这个锁给解开

而这个互斥对象作用域还没有结束

那么这个锁就一直处于锁定状态

其它的线程就不能够用

所以这个时候要特别注意这一点

典型的解决方案是什么呢

就是你使用一个复合语句结构

可以把这个临界区这段代码

就用复合语句块把它括起来

在语句块开头加这个锁

在语句块的结尾

临界区做完了嘛

那个对象自动地就会被析构

锁就会被打开

当多个资源需要竞争访问的时候

有好几个互斥需要竞争访问

那么这个时候就需要特别注意

为了避免死锁

你可以使用标准库的std::lock()、std::try_lock()

去尝试去锁定它

标准库里边的这两个函数lock()和try_lock()

能够预先发现死锁的存在

并且避免它

讲起来会觉得很奇怪

不知道该怎么用

我们看具体的例子 你就明白了

这个例子我们会使用互斥管理策略类

来重现实现我们原来的线程函数

这是我们的劳工类

在这个线程函数内部

我使用一个复合的花括号对

你看它顶头没有if 也没有switch

也没有for、while 什么东西都没有

就是一个孤零零的花括号对

就构造一个语句块

在这个语句块的开头

我定义一个std::lock_guard〈std::mutex〉

这个类的一个对象 locker

传x 那个互斥进去

就是锁定一个互斥

因为它是一个互斥管理类嘛

所以它的模板

那个参数必须是一个互斥

构造它的时候就是locker(x)

传一个互斥进去

这个locker这个对象就被我们构造好了

缺省的时候一构造完毕

它就把它加锁

如果加不上

这个函数 这个线程就会被阻塞

函数被阻塞了

那么就做不下去了

所以这段代码一过去

锁一定是拿到了

临界区代码执行就完了

花括号对结束

这个作用域就结束了

locker就会被释放

不用等到输出完毕之后

才销毁那个locker

才释放那个锁 不需要

在这个位置就销毁了 多方便啊

这就是我们的lock_guard

这个管理类模板使用方法

特别简单吧

我们看第二个例子

就是我们刚才转账处理的

那个程序的一个扩展

我们不使用刚才那段代码了

没有那个double数组

我们定义Account类

这样的一个账户类

在这个账户类里面

有两个私有成员

一个是_balance 表示它的余额

一个是_x 就是访问这个对象的时候的

对应的互斥

有几个成员函数 GetBalance()

Increase()、Decrease() 给它加钱、减钱

还有一个函数GetMutex()

得到它的互斥

如果我有一个函数叫Transfer()

我们会传两个账户对象的引用进去

这是我们的Transfer()

这个线程函数

我们会做锁定动作

这是两个账户要传递钱啊 要转账啊

所以我们要锁定两个账户

一个账户要加

一个账户要减嘛 对吧

我们要锁定两个账户

我们用unique_lock锁定from账户

它的对应的互斥

这是locker1

第一个参数传from.GetMutex()

它的策略是adopt_lock

第二个是unique_lock〈std::mutex〉

locker2 to.GetMutex()

获取to的那个账户的互斥

锁定策略也一样

接下来我们锁定它

std::lock() 调用这个全局函数

定义在标准名空间里边的这个函数

锁定它 from.GetMutex(),to.GetMutex()

同时锁定这两个互斥 然后转账

函数一结束 作用域就消失了

locker1、locker2解锁

这两个量就会被释放嘛

它就解锁了 这就很典型

我们在这里构造两个线程对象t1和t2

注意我们要传线程函数Transfer

但是这个Transfer呢

这个线程函数是需要带三个参数的

那我们就需要把这三个参数传进去

但是你要注意

线程函数本身

是在C语言格式里面就有的

所以它只有值传递一种

如果在C++代码里

想要向一个线程函数传递一个引用

你必须明确的用std::ref()

取到这个对象的引用

才能传进去

std::ref(a1)、std::ref(a2)传进去

传转账的数额OK

t2线程也一样

然后t1.join()、t2.join()

等这两个线程做完

你多运行几遍

你看一下你就知道

有的时候t1线程会先做

有的时候t2线程会先做

但不管怎么样

每次操作的时候

转账的这个临界区内部代码

要么不做 要么做完

基于Linux的C++课程列表:

第一讲 C/C++基本语法元素

-1.1 提纲

--LinuxCPP0101

-1.2 程序设计的基本概念

--LinuxCPP0102

-1.3 简单C/C++程序介绍

--LinuxCPP0103

-1.4 程序设计的基本流程

--LinuxCPP0104

-1.5 基本语法元素

--LinuxCPP0105

-1.6 程序设计风格

--LinuxCPP0106

-1.7 编程实践

--LinuxCPP0107

-第一讲 C/C++基本语法元素--编程实践提交入口

第二讲 程序控制结构

-2.1 提纲

--LinuxCPP0201

-2.2 结构化程序设计基础

--LinuxCPP0202

-2.3 布尔数据

--LinuxCPP0203

-2.4 分支结构

--LinuxCPP0204

-2.5 break语句

--LinuxCPP0205

-2.6 循环结构

--LinuxCPP0206

-2.7 编程实践

--LinuxCPP0207

-第二讲 程序控制结构--编程实践提交入口

第三讲 函数

-3.1 提纲

--LinuxCPP0301

-3.2 函数声明、调用与定义

--LinuxCPP0302

-3.3 函数调用栈框架

--LinuxCPP0303

-3.4 编程实践

--LinuxCPP0304

-第三讲 函数--编程实践提交入口

第四讲 算法

-4.1 提纲

--LinuxCPP0401

-4.2 算法概念与特征

--LinuxCPP0402

-4.3 算法描述

--LinuxCPP0403

-4.4 算法设计与实现

--LinuxCPP0404

-4.5 递归算法(一)

--LinuxCPP0405

-4.6 递归算法(二)

--LinuxCPP0406

-4.7 容错与计算复杂度

--LinuxCPP0407

-4.8 编程实践

--LinuxCPP0408

-第四讲 算法--编程实践提交入口

第五讲 程序组织与开发方法

-5.1 提纲

--LinuxCPP0501

-5.2 库与接口

--LinuxCPP0502

-5.3 随机数库(一)

--LinuxCPP0503

-5.4 随机数库(二)

--LinuxCPP0504

-5.5 作用域与生存期

--LinuxCPP0505

-5.6 典型软件开发流程(一)

--LinuxCPP0506

-5.7 典型软件开发流程(二)

--LinuxCPP0507

-5.8 编程实践

--LinuxCPP0508

-第五讲 程序组织与开发方法--编程实践提交入口

第六讲 复合数据类型

-6.1 提纲

--LinuxCPP0601

-6.2 字符

--LinuxCPP0602

-6.3 数组(一)

--LinuxCPP0603

-6.4 数组(二)

--LinuxCPP0604

-6.5 结构体

--LinuxCPP0605

-6.6 编程实践

--LinuxCPP0606

-第六讲 复合数据类型--编程实践提交入口

第七讲 指针与引用

-7.1 提纲

--LinuxCPP0701

-7.2 指针基本概念

--LinuxCPP0702

-7.3 指针与函数

--LinuxCPP0703

-7.4 指针与复合数据类型(一)

--LinuxCPP0704

-7.5 指针与复合数据类型(二)

--LinuxCPP0705

-7.6 字符串

--LinuxCPP0706

-7.7 动态存储管理(一)

--LinuxCPP0707

-7.8 动态存储管理(二)

--LinuxCPP0708

-7.9 引用

--LinuxCPP0709

-7.10 编程实践

--LinuxCPP0710

-第七讲 指针与引用--编程实践提交入口

第八讲 链表与程序抽象

-8.1 提纲

--LinuxCPP0801

-8.2 数据抽象(一)

--LinuxCPP0802

-8.3 数据抽象(二)

--LinuxCPP0803

-8.4 链表(一)

--LinuxCPP0804

-8.5 链表(二)

--LinuxCPP0805

-8.6 链表(三)

--LinuxCPP0806

-8.7 链表(四)

--LinuxCPP0807

-8.8 函数指针(一)

--LinuxCPP0808

-8.9 函数指针(二)

--LinuxCPP0809

-8.10 抽象链表(一)

--LinuxCPP0810

-8.11 抽象链表(二)

--LinuxCPP0811

-8.12 编程实践

--LinuxCPP0812

-第八讲 链表与程序抽象--编程实践提交入口

第九讲 类与对象

-9.1 提纲

--LinuxCPP0901

-9.2 程序抽象与面向对象

--LinuxCPP0902

-9.3 类类型

--LinuxCPP0903

-9.4 对象(一)

--LinuxCPP0904

-9.5 对象(二)

--LinuxCPP0905

-9.6 类与对象的成员(一)

--LinuxCPP0906

-9.7 类与对象的成员(二)

--LinuxCPP0907

-9.8 类与对象的成员(三)

--LinuxCPP0908

-9.9 继承(一)

--LinuxCPP0909

-9.10 继承(二)

--LinuxCPP0910

-9.11 继承(三)

--LinuxCPP0911

-9.12 多态(一)

--LinuxCPP0912

-9.13 多态(二)

--LinuxCPP0913

-9.14 编程实践

--LinuxCPP0914

-第九讲 类与对象--编程实践提交入口

第十讲 操作符重载

-10.1 提纲

--LinuxCPP1001

-10.2 四则运算符重载(一)

--LinuxCPP1002

-10.3 四则运算符重载(二)

--LinuxCPP1003

-10.4 关系与下标操作符重载

--LinuxCPP1004

-10.5 赋值操作符重载(一)

--LinuxCPP1005

-10.6 赋值操作符重载(二)

--LinuxCPP1006

-10.7 赋值操作符重载(三)

--LinuxCPP1007

-10.8 赋值操作符重载(四)

--LinuxCPP1008

-10.9 赋值操作符重载(五)

--LinuxCPP1009

-10.10 流操作符重载(一)

--LinuxCPP1010

-10.11 流操作符重载(二)

--LinuxCPP1011

-10.12 流操作符重载(三)

--LinuxCPP1012

-10.13 操作符重载总结

--LinuxCPP1013

-10.14 编程实践

--LinuxCPP1014

-第十讲 操作符重载--编程实践提交入口

第十一讲 泛型编程

-11.1 提纲

--LinuxCPP1101

-11.2 泛型编程概览

--LinuxCPP1102

-11.3 异常处理机制(一)

--LinuxCPP1103

-11.4 异常处理机制(二)

--LinuxCPP1104

-11.5 运行期型式信息(一)

--LinuxCPP1105

-11.6 运行期型式信息(二)

--LinuxCPP1106

-11.7 模板与型式参数化

--LinuxCPP1107

-11.8 题外话:术语翻译

--LinuxCPP1108

-11.9 泛型编程实践(一)

--LinuxCPP1109

-11.10 泛型编程实践(二)

--LinuxCPP1110

-11.11 泛型编程实践(三)

--LinuxCPP1111

-11.12 泛型编程实践(四)

--LinuxCPP1112

-11.13 泛型编程实践(五)

--LinuxCPP1113

-11.14 泛型编程实践(六)

--LinuxCPP1114

-11.15 泛型编程实践(七)

--LinuxCPP1115

-11.16 泛型编程实践(八)

--LinuxCPP1116

-11.17 泛型编程实践(九)

--LinuxCPP1117

-11.18 泛型编程实践(十)

--LinuxCPP1118

-11.19 编程实践

--LinuxCPP1119

-第十一讲 泛型编程--编程实践提交入口

第十二讲 Linux系统编程基础

-12.1 提纲

--LinuxCPP1201

-12.2 程序执行环境(一)

--LinuxCPP1202

-12.3 程序执行环境(二)

--LinuxCPP1203

-12.4 程序执行环境(三)

--LinuxCPP1204

-12.5 程序执行环境(四)

--LinuxCPP1205

-12.6 输入输出(一)

--LinuxCPP1206

-12.7 输入输出(二)

--LinuxCPP1207

-12.8 文件系统

--LinuxCPP1208

-12.9 设备

--LinuxCPP1209

-12.10 库(一)

--LinuxCPP1210

-12.11 库(二)

--LinuxCPP1211

-12.12 makefile文件(一)

--LinuxCPP1212

-12.13 makefile文件(二)

--LinuxCPP1213

-12.14 makefile文件(三)

--LinuxCPP1214

-12.15 编程实践

--LinuxCPP1215

-第十二讲 Linux系统编程基础--编程实践提交入口

第十三讲 进程编程

-13.01 提纲

--LinuxCPP1301

-13.02 进程基本概念

--LinuxCPP1302

-13.03 信号

--LinuxCPP1303

-13.04 进程管理(一)

--LinuxCPP1304

-13.05 进程管理(二)

--LinuxCPP1305

-13.06 进程管理(三)

--LinuxCPP1306

-13.07 进程间通信(一)

--LinuxCPP1307

-13.08 进程间通信(二)

--LinuxCPP1308

-13.09 进程间通信(三)

--LinuxCPP1309

-13.10 进程间通信(四)

--LinuxCPP1310

-13.11 进程池

--LinuxCPP1311

-13.12 编程实践

--LinuxCPP1312

-第十三讲 进程编程--编程实践提交入口

第十四讲 线程编程

-14.1 提纲

--LinuxCPP1401

-14.2 线程基本概念

--LinuxCPP1402

-14.3 线程管理(一)

--LinuxCPP1403

-14.4 线程管理(二)

--LinuxCPP1404

-14.5 线程管理(三)

--LinuxCPP1405

-14.6 线程管理(四)

--LinuxCPP1406

-14.7 线程同步机制(一)

--LinuxCPP1407

-14.8 线程同步机制(二)

--LinuxCPP1408

-14.9 C++11线程库(一)

--LinuxCPP1409

-14.10 C++11线程库(二)

--LinuxCPP1410

-14.11 C++11线程库(三)

--LinuxCPP1411

-14.12 C++11线程库(四)

--LinuxCPP1412

-14.13 C++11线程库(五)

--LinuxCPP1413

-14.14 编程实践

--LinuxCPP1414

-第十四讲 线程编程--编程实践提交入口

第十五讲 网络编程

-15.1 提纲

--LinuxCPP1501

-15.2 Internet网络协议

--LinuxCPP1502

-15.3 套接字(一)

--LinuxCPP1503

-15.4 套接字(二)

--LinuxCPP1504

-15.5 编程实践

--LinuxCPP1505

-第十五讲 网络编程--编程实践提交入口

课程文档

-课程PDF文件

LinuxCPP1410笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。