什么是自旋锁

多线程中,对共享资源进行访问,为了防止并发引起的相关问题,通常都是引入锁的机制来处理并发问题。

获取到资源的线程A对这个资源加锁,其他线程比如B要访问这个资源首先要获得锁,而此时A持有这个资源的锁,只有等待线程A逻辑执行完,释放锁,这个时候B才能获取到资源的锁进而获取到该资源。

这个过程中,A一直持有着资源的锁,那么没有获取到锁的其他线程比如B怎么办?通常就会有两种方式:

1. 一种是没有获得锁的进程就直接进入阻塞(BLOCKING),这种就是互斥锁

2. 另外一种就是没有获得锁的进程,不进入阻塞,而是一直循环着,看是否能够等到A释放了资源的锁。

上述的两种方式,学术上,就有几种不同的定义方式,大学的时候 学习的是C++, 《C++ 11》中就有这样的描述:

自旋锁(spin lock)是一种非阻塞锁,也就是说,如果某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。

互斥量(mutex)是阻塞锁,当某线程无法获取锁时,该线程会被直接挂起,该线程不再消耗CPU时间,当其他线程释放锁后,操作系统会激活那个被挂起的线程,让其投入运行。

而《linux内核设计与实现》经常提到两种态,一种是内核态,一种是用户态,对于自旋锁来说,自旋锁使线程处于用户态,而互斥锁需要重新分配,进入到内核态。这里大家对内核态和用户态有个初步的认知就行了,用户态比较轻,内核态比较重。用户态和内核态这个也是linux中必备的知识基础,借鉴这个,可以进行很多程序设计语言API上的优化,就比如说javaio的部分,操作io的时候,先是要从用户态,进入内核态,再用内核态去操作输入输出设备的抽象,这里减少用户态到内核态的转换就是新io的一部分优化,后面再聊。

wiki中的定义如下:

自旋锁是计算机科学用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。

自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。因此操作系统的实现在很多地方往往用自旋锁。Windows操作系统提供的轻型读写锁(SRW Lock)内部就用了自旋锁。显然,单核CPU不适于使用自旋锁,这里的单核CPU指的是单核单线程的CPU,因为,在同一时间只有一个线程是处在运行状态,假设运行线程A发现无法获取锁,只能等待解锁,但因为A自身不挂起,所以那个持有锁的线程B没有办法进入运行状态,只能等到操作系统分给A的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。(红字部分是我给wiki编辑的词条,单核CPU不适合自旋锁,这个也只是针对单核单线程的情况,现在的技术基本单核都是支持多线程的)

为什么要使用自旋锁
互斥锁有一个缺点,他的执行流程是这样的 托管代码  - 用户态代码 - 内核态代码、上下文切换开销与损耗,假如获取到资源锁的线程A立马处理完逻辑释放掉资源锁,如果是采取互斥的方式,那么线程B从没有获取锁到获取锁这个过程中,就要用户态和内核态调度、上下文切换的开销和损耗。所以就有了自旋锁的模式,让线程B就在用户态循环等着,减少消耗。

自旋锁比较适用于锁使用者保持锁时间比较短的情况,这种情况下自旋锁的效率要远高于互斥锁。

自旋锁可能潜在的问题
过多占用CPU的资源,如果锁持有者线程A一直长时间的持有锁处理自己的逻辑,那么这个线程B就会一直循环等待过度占用cpu资源
递归使用可能会造成死锁,不过这种场景一般写不出来。

 

总结:自旋的意思就是while()循环这样的等待,这样是会一直消耗cpu的,不会造成任务切换,而且只有多核才能用,单核不是真正的并行,所以不挂起是不行的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章