什么是自旋锁?
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting,这样会使cpu资源浪费。所以自旋锁一般使用在加锁代码段执行耗时非常短的地方。
使用swift实现一个自旋锁
public class BMLock {
private var locked = 0
func lock() {
while !OSAtomicCompareAndSwapLongBarrier(0, 1, &locked) {}
}
func unlock() {
OSAtomicCompareAndSwapLongBarrier(1, 0, &locked)
}
}
func OSAtomicCompareAndSwapLongBarrier(_ __oldValue: Int, _ __newValue: Int, _ __theValue: UnsafeMutablePointer!) -> Bool
这里使用了CompareAndSet(CAS)。
伪代码是这样的
func OSAtomicCompareAndSwapLongBarrier(_ __oldValue: Int, _ __newValue: Int, _ __theValue: Int) -> Bool {
if oldValue == theValue {
theValue = newValue
return true
} else {
return false
}
}
如果需要递归
public class BMLock {
private var thread:UnsafeMutableRawPointer?
private var count = 0
public func lock() {
if OSAtomicCompareAndSwapPtrBarrier(pthread_self(), pthread_self(), &thread) {
count += 1
return
}
while !OSAtomicCompareAndSwapPtrBarrier(nil, pthread_self(), &thread) {usleep(10)}//usleep10提高性能
}
public func unlock() {
if count > 0 {
count -= 1
} else {
OSAtomicCompareAndSwapPtrBarrier(pthread_self(), nil, &thread)
}
}
}
CompareAndSet通过原子操作实现了CAS操作,最底层基于汇编语言实现。
简单说一下原子操作的概念,“原子”代表最小的单位,所以原子操作可以看做最小的执行单位,该操作在执行完毕前不会被任何其他任务或事件打断。
这里还需要使用带barrier结尾的方法。
Memory ordering
Memory ordering用来描述系统中的processor对内存的操作如何对其它processor可见(可见的定义见前面的描述)。同时需要说明的是,大多数文献都采用reorder这个表达方式,是从执行等价的角度来描述的:比如P1上执行两个写操作WRITE(A)和WRITE(B),如果对于观察者P2来说P1|WRITE(B)先于P2|WRITE(A)可见,那么就可以认为P1的写操作发生了reorder。对读操作也是类似的。
影响memoryordering的因素很多,包括:
体系结构,X86和ARM的memory ordering就截然不同;
ARM的memory order属于weak order,与SC差距极大。如果涉及到免锁设计,对ARM体系结构, memorybarrier的使用是不可避免的。
自旋锁缺点
自旋锁会存在优先级反转问题。
具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。
苹果的OSSpinLock因为存在优先级反转问题,在 iOS 10/macOS 10.12 发布时,苹果提供了新的 os_unfair_lock 作为 OSSpinLock 的替代,并且将 OSSpinLock 标记为了 Deprecated。