本文首发于 我的博客ios
多线程中的锁一般分为互斥锁和自旋锁,这篇文章主要向你们介绍一些自旋锁的原理以及atomic的底层实现。git
⚛维基百科上对自旋锁的解释:程序员
自旋锁 是计算机科学用于多线程同步的一种锁,线程反复检查锁变量是否可用。因为线程在这一过程当中保持执行,所以是一种
忙等
(忙碌等待)。一旦获取了自旋锁,线程会一直持有该锁,直至显式释放自旋锁。github获取、释放自旋锁,其实是读写自旋锁的存储内存或寄存器。所以这种读写操做必须是原子的
(atomic)
。一般用text-and-set
原子操做来实现。安全
自旋锁
的核心就是忙等,尝试自定义一个自旋锁以下:markdown
struct spinlock {
int flag;
};
@implementation TPSpinLock {
spinlock _lock;
}
- (instancetype)init {
self = [super init];
if (self) {
_lock = spinlock{0};
}
return self;
}
- (void)lock {
while (test_and_set(&_lock.flag, 1)) {
// wait
}
}
- (void)unlock {
_lock.flag = 0;
}
int test_and_set(int *old_ptr, int _new) {
int old = *old_ptr;
*old_ptr = _new;
return old;
}
@end
复制代码
如上述代码,咱们自定义了test_and_set
方法,当线程1
进行lock
操做的时候会传入flag = 0
,test_and_set
方法返回0
的同时并将flag = 1
,这个时候线程2
执行lock
的时候一直返回1
,那么就一直执行while(1)
处于等待状态,直到线程1
执行unlock
将flag = 0
这个时候就打破while
循环,线程2
就能继续执行并加锁。多线程
提及自旋锁,无不联想到属性的原子操做,即 atomic
异步
atomic
底层是如何实现的?atomic
绝对安全吗?带着这些问题咱们对 atomic
进行探讨,咱们来到 objc源码 处进行查看,atomic
既然是修饰property
的,那么必然会跟property
的set
和get
方法相关,咱们找到了相关方法的实现:async
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
// 原子操做判断
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
复制代码
set
方法atomic
那块加了判断,若是是原子性就会进行加锁和解锁操做。oop
再看 get
方法:
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
复制代码
很明显,也是对原子操做进行加锁处理。
咱们注意到全部的源码中针对加锁的地方都是定义为spinlock
,也就是自旋锁,因此一般被人问到咱们atomic
底层是什么的时候,咱们都回答 自旋锁
,结合YY大神的再也不安全的OSSpinLock 一文,能够看出Apple
已经弃用OSSpinLock
了,内部确以下述代码那样是用os_unfair_lock
来实现的,探其底层执行lock
和unlock
的实际上是mutex_t
,也就是互斥锁
。
// property的set方法
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
// atomic中用到的锁
using spinlock_t = mutex_tt<LOCKDEBUG>;
// mutex_tt 的结构
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
public:
constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) {
lockdebug_remember_mutex(this);
}
constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { }
void lock() {
lockdebug_mutex_lock(this);
os_unfair_lock_lock_with_options_inline
(&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION);
}
void unlock() {
lockdebug_mutex_unlock(this);
os_unfair_lock_unlock_inline(&mLock);
}
// 上述lock方法的实现
void
lockdebug_mutex_lock(mutex_t *lock)
{
auto& locks = ownedLocks();
if (hasLock(locks, lock, MUTEX)) {
_objc_fatal("deadlock: relocking mutex");
}
setLock(locks, lock, MUTEX);
}
复制代码
因此说 atomic
的本质并非自旋锁,至少当前不是,我查询了 objc
以前的源码发现老版本的 atomic
的实现,确实不同:
typedef uintptr_t spin_lock_t;
OBJC_EXTERN void _spin_lock(spin_lock_t *lockp);
OBJC_EXTERN int _spin_lock_try(spin_lock_t *lockp);
OBJC_EXTERN void _spin_unlock(spin_lock_t *lockp);
复制代码
由此可知:
atomic
原子操做只是对setter
和getter
方法进行加锁
那么第二个问题来了:atomic
绝对安全吗?咱们接着分析,首先看下面的代码,最终的 number
会是多少?20000
?
@property (atomic, assign) NSInteger number;
- (void)atomicTest {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10000; i ++) {
self.number = self.number + 1;
NSLog(@"A-self.number is %ld",self.number);
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10000; i ++) {
self.number = self.number + 1;
NSLog(@"B-self.number is %ld",self.number);
}
});
}
复制代码
打印结果:
NO
并非 20000
,这是为啥呢?咱们的 number
是 atomic
进行加锁了啊,为何还会出现线程安全问题。其实答案上文已经有了,只是须要咱们仔细去品,atomic
只是针对 setter
和 getter
方法进行加锁,上述代码有两个异步线程同时执行,若是某个时间 A线程
执行到getter
方法,以后 cpu
当即切换到 线程B
去执行他的get方法那么这个时候他们进行 +1
的处理并执行setter
方法,那么两个线程的 number
就会是同样的结果,这样咱们的 +1
就会出现线程安全问题,就会致使咱们的数字出现误差,那么咱们找一找打印数字里是否有重复的:
功夫不负有心人,咱们果真找到了重复的,那么基于咱们 20000
的循环次数少个百八十的太正常不过了。
自旋锁
不一样于互斥锁
若是访问的资源被占用,它会处于 忙等
状态。自旋锁因为一直处于忙等状态因此他在线程锁被释放的时候会当即获取而不用唤醒,因此其执行效率是很高的,尤为是在多核的cpu上运行效率很高,可是其忙等的状态会消耗cpu
的性能,因此其性能比互斥锁要低不少。atomic
的底层实现,老版本是自旋锁
,新版本是互斥锁
。atomic
并非绝对线程安全,它能保证代码进入 getter
和 setter
方法的时候是安全的,可是并不能保证多线程的访问状况下是安全的,一旦出了 getter
和 setter
方法,其线程安全就要由程序员本身来把握,因此 atomic
属性和线程安全并无必然联系。