1. 锁的分类
锁的学习资料:并发支持库
1.1. 按照能否实现互斥量分类
互斥算法避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。
- mutex,timed_mutex,recursive_mutex,recursive_timed_mutex,shared_mutex,shared_timed_mutex
- counting_semaphore,binary_semaphore
- atomic实现的自旋锁
1.2. 按照能否提供锁获取的超时时间分类
尝试锁定互斥,若互斥在指定的时限时期中不可用则返回。或,尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回。
- timed_mutex,recursive_timed_mutex,shared_timed_mutex
- counting_semaphore,binary_semaphore
1.3. 按照能否实现条件变量分类
条件变量是允许多个线程相互交流的同步原语。它允许一定量的线程等待(可以定时)另一线程的提醒,然后再继续。条件变量始终关联到一个互斥。
- condition_variable,condition_variable_any
- binary_semaphore
- atomic实现的等待锁
1.4. 按照能否获取锁的上锁状态分类
- atomic实现的自旋锁
- unique_lock(它不是一种锁,只是一种通用互斥管理器)
1.5. 按照锁能否共享分类
- shared_mutex,shared_timed_mutex
1.6. 按照锁能否多次获取分类
连续在一个锁的作用域内多次获取同一把锁。
- recursive_mutex,recursive_timed_mutex
2. 根据场景分析锁的选择
2.1. A和B同等竞争锁
模型1:
假设办公室有一台打印机供大家使用,办公室的员工竞争使用打印机。A和B都想使用打印机,A先到,则B和C需要等待A使用完在使用,此时B和C就站在打印机旁边等待着A用完,A用完之后释放打印机,B和C谁先抢到谁用。
分析:
在这种场景下,办公室的人员都有一样的优先级去使用打印机,一个人使用的时候另一个人必须等待,不能抢占。而且B和C比较傻,就等在打印机旁边等A用完,不先去工作(死等)。
锁选择:
能实现互斥量的锁都能满足这个要求:
- mutex
- counting_semaphore,binary_semaphore
- atomic实现的自旋锁
模型2:
模型1中的问题就是,A在使用的时候B和C就傻傻等着,他们完全可以先去工作嘛(等待机制),等A使用完的时候在办公室说一声(通知机制),B和C再去抢嘛。
分析:
这种场景就涉及到了通知和等待机制,A是通知者,B和C是等待者,就需要能提供条件变量的锁。
锁选择:
- condition_variable,condition_variable_any
- binary_semaphore
- atomic实现的等待锁
模型3:
针对模型1和模型2我们在生活中还可能遇见一种情况:我的打印任务比较紧急,领导急着用材料,此时我的首选是等着本办公室的打印机去打印(等着A用完),但是我不是一直等,而是等待一定时间后A如果还没用完,我就去楼下打印店打印了。因为我的打印任务虽然比较急(虽然急,但是我不能抢A的打印机,因为我们属于同等优先级),但是我又懒得跑腿,就先等一会,不行了我再去楼下打印店。
锁选择:
只要能提供锁获取的超时时间的都可以:
- timed_mutex,recursive_timed_mutex,shared_timed_mutex
- counting_semaphore,binary_semaphore
2.2. A需要等待B而B不需要等待A
模型4:
还是以打印机模型为例,不过引入一个领导来丰富我们的模型,即普通员工使用的时候需要看领导在不在使用打印机,领导在使用,我们就需要等领导使用完了再使用。而普通员工在使用的时候领导有更高优先级,可以直接来使用打印机,不管员工是否正在使用。
分析:
这种就是领导有更高优先级,即员工需要看领导状态,领导不需要看员工状态,属于单方面等待。
锁选择:
- atomic实现的等待锁
2.3. A可被B抢占
模型5:
模型4的问题就是,领导来了不管员工有没有用打印机,领导就开始直接用。这样员工都没有退出正在打印任务的时间,两个人同时使用打印机,可能会造成数据混乱的问题。领导完全可以先来说一声我要用打印机了,你先退一下,等我用完了你在用。这样员工就有了优雅的退出打印的时机,退出完了,告诉领导一声我退出了,领导开始来打印,领导打印完了,员工接着来打印。
分析:
这种模型就需要领导先来告知一声(即把一个变量设置为true),然后领导等待员工退出(等待机制)。当员工收到告知时(检测到变量为true),就停止打印,并通知领导可以来用了(通知机制)。
锁选择:
- 一个标志变量 + 可实现条件变量的锁
2.4. A先于B发生
模型6:
这种模型就是B事件的发生依赖于A事件先发生。即,你想使用打印机之前,得先买台打印并安装好吧。
锁选择:
这种锁就需要能实现条件变量的锁,即当买打印机并安装打印机这个条件为真,你才可能去使用打印。
- condition_variable,condition_variable_any
- binary_semaphore
- atomic实现的等待锁
2.5. A的发生需满足一定条件
模型7:
安装一台打印机需要满足一定的条件:经费审批,采购,选择合适的安装地点等。等这些条件都满足的时候你才能安装打印机。这种模型和模型6比较类似,不过模型5一般是一个条件满足后,B事件就能立马进行,而这种一般是需要满足多个条件后B事件才能进行。
锁选择:
这种锁就需要能实现条件变量的锁,不过要满足的条件更多,而不是一个条件:
- condition_variable,condition_variable_any
2.6. 检测A的状态
模型8:
我不用打印机,我就看看打印机现在有没有人用,有人用我就去干别的事,没人用我也不一定用(想用就用,不想用就等会)。
锁选择:
这种模型就是简单的查询一下锁的状态,即打印机现在有没有被占用(占用即上锁):
- atomic实现的自旋锁
- unique_lock(它不是一种锁,只是一种通用互斥管理器)
2.7. 共享A
模型9:
一台电视,大家可以多个人一起看,但是同一个时间只能有一个人换台。
锁选择:
这种模型就是读(看电视)可以共享,修改(换电视台)是互斥的。但是如果大家看这个台都津津有味,我不想看这个台,那我岂不是很不好意思换台?就可能会出现写被读饿死的情况。就是一直有读者来访问这个变量,我写者就无法进行。为了打破这种写被读饿死的问题,写的优先级比读高。即,我是这台电视的主人,我不管你们看的是否津津有味,我不想看这个台了,我就要换台。
- shared_mutex,shared_timed_mutex
3. 深入理解C++内存模型
- 深入理解C11/C++11内存模型
- 内存顺序(Memory Order)问题(一)
- 内存顺序(Memory Order)问题(二)
4. Linux锁机制
- Linux锁机制