本文首发于:
掘金
对于初次接触线程同步的前端来说,总是对互斥锁、条件变量、信号量等术语傻傻分不清楚,这里根据自己的理解简单做下总结,如有疏漏之处,欢迎大家批评指正。
互斥锁
在多线程环境中往往存在因某一资源被同时访问导致该资源不一致的问题,互斥锁
通过排它性,即同时只允许一个访问者对其进行访问来保证资源的有效同步,但它无法限制线程对该资源的访问顺序,因此线程对资源的访问也是无序的。
自旋锁
在互斥锁
中,如果线程 A 在请求锁的时候发现该锁已被线程 B 霸占,那么此时线程 A 便会进入休眠状态,直到锁被线程 B 释放后被系统唤醒。但在自旋锁
中,线程 A 在发现锁被霸占时并不进入休眠,而是一直循环查看锁的持有者是否已经释放了该锁。初看起来,这种霸占CPU资源的做法极其低效,但与互斥锁
仔细对比后我们可以发现它有以下几个优点:
- 实现简单:只需死循环检测锁状态即可,没有
互斥锁
休眠、唤醒所涉及的一系列上下文切换、CPU抢占等各种复杂流程。 - 高效:正是由于实现极其简单,所以它在一些情况下极其高效。
当然,上面的高效也是有条件的,由于它占用CPU资源,所以它主要适用于以下场景:
- 临界区持锁时间较短且CPU资源不紧张。
- 多用于多核环境。
递归锁
递归锁
又叫可重入锁
,与 互斥锁
的主要区别是在同一个线程内可以多次获得锁资源,别的线程必须等待该线程释放相应次数的锁才能获得,其主要目的是为了解决同一进程内的死锁问题,但在不同的线程中,它与互斥锁
并没有什么区别。
读写锁
上面介绍的互斥锁
、自旋锁
、递归锁
都属于排它锁,即一个线程获得锁资源后,其他线程必须等待直到该锁被释放。但在某些读多写少的情况下,这样的机制难免有些低效,因此读写锁
就是为解决这样的问题而诞生的。读写锁分读锁和写锁,其特点如下:
- 读锁:如果线程 A 获得了读锁,线程 B 可以获得读锁,但不可以获得写锁。
- 写锁:如果线程 A 获得了写锁,线程 B 即不可以获得读锁,也不可以获得写锁。
- 写锁优先:如果线程 A 申请获得写锁,线程 B 申请获得读锁,优先给线程 A 分配写锁。
条件变量
条件变量
主要适用于一个线程需要等待某个共享资源满足某个条件后进行一系列同步操作的场景。它主要包含:
- 等待某个条件成立的等待线程。
- 满足某个条件成立的信号发送线程。
它一般与互斥锁
配合使用(主要用来保护共享资源),在等待线程中,如果条件不成立,该线程会自动阻塞,并释放掉等待状态改变的互斥锁,如果信号发送线程改变了条件,它发送信号给关联的条件变量,并唤醒等待线程,等待线程重新获得互斥锁,重新评估条件。
信号量
信号量
主要适用于一个线程需要等待另外一个个线程完成一些操作后再继续执行自己操作的场景,它与上面各种锁的最大区别是:
- 锁要解决的是共享资源的同步问题,而
信号量
要解决的是线程之间任务同步问题。 - 锁必须在同一进程进行加锁和解锁操作,而
信号量
可以通过一个线程中得到,在另一个线程中释放。
认真回味下我们会发现信号量
要解决的问题也可以使用条件变量
来处理,相对于信号量
,条件变量
主要有以下不足:
-
条件变量
需要借助全局共享变量以及互斥锁
来达到状态的检测。 -
条件变量
适用于多线程环境,无法适用于多进程环境。
屏障
屏障
是一种协调多个线程进行工作,即允许某个线程等待直到所有的合作线程达到某一个条件,然后从该条件下继续执行的同步机制。
总结
本文对线程同步中所涉及到的术语的特点及适用场景进行了简单的总结,如果你在阅读过程中发现任何错误,欢迎留言指正,我们一起学习一起进步。^ _ ^