C++11 锁 lock
转自:https://www.cnblogs.com/diegodu/p/7099300.html
互斥(Mutex: Mutual Exclusion)
下面的代码中两个线程连续的往int_set中插入多个随机产生的整数。
1 |
std::set<int> int_set; |
由于std::set::insert不是多线程安全的,多个线程同时对同一个对象调用insert其行为是未定义的(通常导致的结果是程序崩溃)。因此需要一种机制在此处对多个线程进行同步,保证任一时刻至多有一个线程在调用insert函数。
C++11提供了4个互斥对象(C++14提供了1个)用于同步多个线程对共享资源的访问。
| 类名 | 描述 |
|---|---|
| std::mutex | 最简单的互斥对象。 |
| std::timed_mutex | 带有超时机制的互斥对象,允许等待一段时间或直到某个时间点仍未能获得互斥对象的所有权时放弃等待。 |
| std::recursive_mutex | 允许被同一个线程递归的Lock和Unlock。 |
| std::recursive_timed_mutex | 顾名思义(bù jiě shì)。 |
| std::shared_timed_mutex(C++14) | 允许多个线程共享所有权的互斥对象,如读写锁,本文不讨论这种互斥。 |
锁(Lock)
这里的锁是动词而非名词,互斥对象的主要操作有两个加锁(lock)和释放锁(unlock)。当一个线程对互斥对象进行lock操作并成功获得这个互斥对象的所有权,在此线程对此对象unlock前,其他线程对这个互斥对象的lock操作都会被阻塞。修改前面的代码在两个线程中对共享资源int_set执行insert操作前先对互斥对象mt进行加锁操作,待操作完成后再释放锁。这样就能保证同一时刻至多只有一个线程对int_set对象执行insert操作。
1 |
std::set<int> int_set; |
使用RAII管理互斥对象
在使用锁时应避免发生死锁(Deadlock)。前面的代码倘若一个线程在执行第10行的int_set.insert时抛出了异常,会导致第11行的unlock不被执行,从而可能导致另一个线程永远的阻塞在第9行的lock操作。类似的情况还有比如你写了一个函数,在进入函数后首先做的事情就是对某互斥对象执行lock操作,然而这个函数有许多的分支,并且其中有几个分支要提前返回。因此你不得不在每个要提前返回的分支在返回前对这个互斥对象执行unlock操作。一但有某个分支在返回前忘了对这个互斥对象执行unlock,就可能会导致程序死锁。
为避免这类死锁的发生,其他高级语言如C#提供了lock关键字、Java提供了synchronized关键字,它们都是通过finally关键字来实现的。比如对于C#
1 |
lock(x) |
等价于
1 |
System.Object obj = (System.Object)x; |
然而C++并没有try-finally,事实上C++并不需要finally。C++通常使用RAII(Resource Acquisition Is Initialization)来自动管理资源。如果可能应总是使用标准库提供的互斥对象管理类模板。
| 类模板 | 描述 |
|---|---|
| std::lock_guard | 严格基于作用域(scope-based)的锁管理类模板,构造时是否加锁是可选的(不加锁时假定当前线程已经获得锁的所有权),析构时自动释放锁,所有权不可转移,对象生存期内不允许手动加锁和释放锁。 |
| std::unique_lock | 更加灵活的锁管理类模板,构造时是否加锁是可选的,在对象析构时如果持有锁会自动释放锁,所有权可以转移。对象生命期内允许手动加锁和释放锁。 |
| std::shared_lock(C++14) | 用于管理可转移和共享所有权的互斥对象。 |
使用std::lock_guard类模板修改前面的代码,在lck对象构造时加锁,析构时自动释放锁,即使insert抛出了异常lck对象也会被正确的析构,所以也就不会发生互斥对象没有释放锁而导致死锁的问题。
1 |
std::set<int> int_set; |
互斥对象管理类模板的加锁策略
前面提到std::lock_guard、std::unique_lock和std::shared_lock类模板在构造时是否加锁是可选的,C++11提供了3种加锁策略。
| 策略 | tag type | 描述 |
|---|---|---|
| (默认) | 无 | 请求锁,阻塞当前线程直到成功获得锁。 |
| std::defer_lock | std::defer_lock_t | 不请求锁。 |
| std::try_to_lock | std::try_to_lock_t | 尝试请求锁,但不阻塞线程,锁不可用时也会立即返回。 |
| std::adopt_lock | std::adopt_lock_t | 假定当前线程已经获得互斥对象的所有权,所以不再请求锁。 |
下表列出了互斥对象管理类模板对各策略的支持情况。
| 策略 | std::lock_guard | std::unique_lock | std::shared_lock |
|---|---|---|---|
| (默认) | √ | √ | √(共享) |
| std::defer_lock | × | √ | √ |
| std::try_to_lock | × | √ | √ |
| std::adopt_lock | √ | √ | √ |
下面的代码中std::unique_lock指定了std::defer_lock。
1 |
std::mutex mt; |
对多个互斥对象加锁
在某些情况下我们可能需要对多个互斥对象进行加锁,考虑下面的代码
1 |
std::mutex mt1, mt2; |
如果线程1执行到第5行的时候恰好线程2执行到第11行。那么就会出现
- 线程1持有mt1并等待mt2
- 线程2持有mt2并等待mt1
发生死锁。
为了避免发生这类死锁,对于任意两个互斥对象,在多个线程中进行加锁时应保证其先后顺序是一致。前面的代码应修改成
1 |
std::mutex mt1, mt2; |
更好的做法是使用标准库中的std::lock和std::try_lock函数来对多个Lockable对象加锁。std::lock(或std::try_lock)会使用一种避免死锁的算法对多个待加锁对象进行lock操作(std::try_lock进行try_lock操作),当待加锁的对象中有不可用对象时std::lock会阻塞当前线程知道所有对象都可用(std::try_lock不会阻塞线程当有对象不可用时会释放已经加锁的其他对象并立即返回)。使用std::lock改写前面的代码,这里刻意让第6行和第13行的参数顺序不同
1 |
std::mutex mt1, mt2; |
此外std::lock和std::try_lock还是异常安全的函数(要求待加锁的对象unlock操作不允许抛出异常),当对多个对象加锁时,其中如果有某个对象在lock或try_lock时抛出异常,std::lock或std::try_lock会捕获这个异常并将之前已经加锁的对象逐个执行unlock操作,然后重新抛出这个异常(异常中立)。并且std::lock_guard的构造函数lock_guard(mutex_type& m, std::adopt_lock_t t)也不会抛出异常。所以std::lock像下面这么用也是正确
1 |
std::lock(mt1, mt2); |
C++11 锁 lock的更多相关文章
- Java同步锁——lock与synchronized 的区别【转】
在网上看来很多关于同步锁的博文,记录下来方便以后阅读 一.Lock和synchronized有以下几点不同: 1)Lock是一个接口,而synchronized是Java中的关键字,synchroni ...
- 孤荷凌寒自学python第三十九天python 的线程锁Lock
孤荷凌寒自学python第三十九天python的线程锁Lock (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 当多个线程同时操作一个文件等需要同时操作某一对象的情况发生时,很有可能发生冲突, ...
- python从入门到放弃之进程锁lock
# ### lock (互斥锁)"""# 应用在多进程当中# 互斥锁lock : 互斥锁是进程间的get_ticket互相排斥进程之间,谁先抢占到资源,谁就先上锁,等到解 ...
- Java核心知识点学习----线程中如何创建锁和使用锁 Lock,设计一个缓存系统
理论知识很枯燥,但这些都是基本功,学完可能会忘,但等用的时候,会发觉之前的学习是非常有意义的,学习线程就是这样子的. 1.如何创建锁? Lock lock = new ReentrantLock(); ...
- java 锁 Lock接口详解
一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...
- 8. 同步锁Lock
package com.gf.demo07; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Ree ...
- 进程锁Lock
例1: 10个人去买票,先去查询余票,有票就去购买.代码如下: # ticket.py {"ticket": 1} # 只有一张票 import json import time ...
- “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- 6、JUC--同步锁Lock
显示锁 Lock 在Java 5.0之前,协调共享对象的访问时可以使用的机 制只有 synchronized 和 volatile .Java 5.0 后增加了一些 新的机制,但并不是一种替代内置 ...
随机推荐
- 96. Partition List [easy]
Description Given a linked list and a value x, partition it such that all nodes less than x come bef ...
- ubuntu下tensorflow安装
1,安装驱动,cuda,cudnn,参考本人上一篇博客http://www.cnblogs.com/zxg-DL/p/9023601.html 2,安装tensorflow 接下来是关于Tenso ...
- typedef和define一些问题
1. 四个用途 用途一: 定义一种类型的别名,而不只是简单的宏替换.可以用作同时声明指针型的多个对象.比如: char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针 ...
- 20155231 cho3 课下作业
20155231 cho3 课下作业 4 1 通过输入gcc -S -o main.s main.c 将下面c程序"week0603学号.c"编译成汇编代码 int g(int x ...
- 20155308 《Java程序设计》实验五 网络编程与安全
20155308 <Java程序设计>实验五 网络编程与安全 实验内容 任务一 两人一组结对编程: 参考http://www.cnblogs.com/rocedu/p/6766748.ht ...
- # 20155308 2016-2017-2《Java程序设计》课堂实践项目 5月17日
20155308 2016-2017-2<Java程序设计>课堂实践项目 5/17 本次因为git出现了问题,所以没有按时提交我的代码 问题一 在IDEA中对P145 MathTool.j ...
- 20155334 2016-2017-2 《Java程序设计》第三周学习总结
20155334 2016-2017-2 <Java程序设计>第三周学习总结 教材学习内容总结 第四章: 讲的是类类型,使用java撰写程序几乎都在使用对象(Object),要产生对象必须 ...
- Java技术——Interface与abstract类的区别
)抽象类是对类抽象,是面向整个类的自下而上的设计理念,一般是先有各种子类,再有把这些有关系的子类加以抽象为父类的需求.而接口是对行为的抽象,是面向行为的自上而下的设计理念,接口根本就不需要知道子类的存 ...
- easyui树动态加载参考
这篇文章是拷贝的,多谢原作者 友情连接:http://www.jb51.net/article/28771.htm var treeTitle = '选择列表'; var treeUrl = '../ ...
- gdb调试带参数程序
一般来说GDB主要调试的是C/C++的程序.要调试C/C++的程序,首先在编译时,我们必须要 把调试信息加到可执行文件中.使用编译 器(cc/gcc/g++)的 -g 参数可以做到这一点.如: > ...