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 后增加了一些 新的机制,但并不是一种替代内置 ...
随机推荐
- scala (7) Set and Tuple
/** * 不可变长Set集合 */ val set0 = Set(1, 2, 3, 4, 5) //++并没有改变原有的set集合,只是将两个set进行合并形成新的set集合 val newSet0 ...
- VIM - 标准模式下简单操作
1. 概述 标准模式下, 简单操作 移动 删除 复制粘贴 收益 熟练后, 编辑文本基本不需要鼠标操作了 思路 只讲最基本的, 避免初学时的混淆 把基本操作归类了, 方便理解 2. 准备 一篇篇幅较长的 ...
- 20155212——man指令的使用及mypwd的实现
man指令的使用及mypwd的实现 man指令的使用 一.man -k的k参数以及代表的意思 代号 代表內容 1 使用者在shell中可以操作的指令或可执行档 2 系統核心可呼叫的函数与工具等 3 一 ...
- addClass+siblings+removeClass用意:
$(this).addClass("li_add").siblings().removeClass("li_add").children('.floor2'). ...
- WPF MVVM从入门到精通8:数据验证
原文:WPF MVVM从入门到精通8:数据验证 WPF MVVM从入门到精通1:MVVM模式简介 WPF MVVM从入门到精通2:实现一个登录窗口 WPF MVVM从入门到精通3:数据绑定 WPF M ...
- Jumpserver跳板机入门
1.jumpserver安装 1.1.环境介绍 系统: CentOS 7.4.1708IP: 192.168.56.110 [root@linux-node1 ~]# uname -r -.el7.x ...
- 源码阅读-GlobalTimer
最近看到一篇文章推了一个开源项目,GlobalTimer.主要是可以用一个定时器来统一管理多个定时任务,可以针对特定任务进行管理. 一.原理 1.一个公共的timer 2.封装任务到自定义个Event ...
- Scala中==,eq与equals的区别
根据官方API的定义: final def ==(arg0: Any): Boolean The expression x == that is equivalent to if (x eq null ...
- Spring学习(十)-----Spring依赖检查
在Spring中,可以使用依赖检查功能,以确保所要求的属性可设置或者注入. 依赖检查模式 4个依赖检查支持的模式: none – 没有依赖检查,这是默认的模式. simple – 如果基本类型(int ...
- HDU-4055:Number String
链接:HDU-4055:Number String 题意:给你一个字符串s,s[i] = 'D'表示排列中a[i] > a[i+1],s[i] = 'I'表示排列中a[i] < a[i+1 ...