iOS线程安全锁
懒的问苍天 人气:0正文
多线程开发,就会有资源抢占的情况,导致出现我们意想不到的数据问题,我们就需要对数据进行加锁,已保证线程安全.
锁主要分为两大类自旋锁和互斥锁。
- 自旋锁:自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,因此是一种忙等待。自旋锁避免了线程上下文切换的调度开销,因此对于线程只会阻塞很短的时间是很高效的,但是对于比较长时间的阻塞也是比较消耗CPU的。(线程忙等)
- 互斥锁:如果资源已经被占用,资源申请者只能进入睡眠状态。有上下文的切换(主动出让时间片, 线程休眠, 等待下一次唤醒)、CPU的抢占、信号的发送等开销。(线程闲等)
原子属性
我们创建属性一般都会设置属性为非原子属性noatomic, 因为原子属性atomic会有额外的加锁开销,那如果我们创建属性使用原子属性atomic,它能保证property是线程安全的吗?
#import "ViewController.h" @interface ViewController () @property (atomic ,assign) int count; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.count = 0; [self test_atomic]; } - (void)test_atomic { // self.count初始值是10 for (int i = 0; i < 10; i ++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ self.count ++; NSLog(@"%d",self.count); }); } } @end
从上面我们可以看到原子属性atomic不能保证数据的线程安全.下面我们从源码进行分析:在属性的getter/setter方法调用的底层atomic和nonatomic有什么区别。先看看setter方法:objc_setProperty
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) { bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY); bool mutableCopy = (shouldCopy == MUTABLE_COPY); reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy); } void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset{ reallySetProperty(self, _cmd, newValue, offset, true, false, false); } void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, false, false, false); } void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, true, true, false); } void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, false, true, false); }
我们可以看到都是调用的reallySetProperty方法,atomic第五个参数为true,nonatomic为false, copy第六个参数为true, mutableCopy第七个参数为true.
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); }
copy和mutableCopy使用copyWithZone进行新值的copy,其他使用objc_retain增加引用计数.nonatomic直接进行赋值;atomic会使用spinlock_t在赋值之前加锁,赋值之后解锁. 我们再来看看getter方法:objc_getProperty
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); }
我们可以看到nonatomic直接返回,atomic在取值前加锁,取值后解锁,再返回值.
那么原子属性atomic在getter/setter底层有加锁解锁操作,为什么不能保证线程安全的呢?
因为原子属性atomic锁住资源的范围不够大。在self.count --;的时候,既有getter也有setter,可能就出现当getter的时候还没有return出去就被其它线程setter。
OSSpinLock - 自旋锁
OSSpinLock 在iOS10之后被移除了。 被移除的原因是它有一个bug:优先级反转。
优先级反转:当多个线程有优先级的时候,有一个优先级较低的线程先去访问了资源,并是有了OSSpinLock对资源加锁,又来一个优先级较高的线程去访问了这个资源,这个时候优先级较高的线程就会一直占用cpu的资源,导致优先级较低的线程没办法与较高的线程争夺cpu的时间,最后导致最先被优先级较低的线程锁住的资源迟迟不能被释放,从而造成优先级反转的bug。
所以 OSSpinLock使用限制:必须保证所有访问同一资源的线程处于优先级平等的时候,才可以使用。
OSSpinLock已被苹果放弃了,大家也可以放弃它,苹果设计了os_unfair_lock来代替OSSpinLock。
os_unfair_lock - 互斥锁
iOS10之后开始支持,os_unfair_lock 在os库中,使用之前需要导入头文件<os/lock.h>。
#import "ViewController.h" #import <os/lock.h> @interface ViewController () @property (nonatomic ,assign) int count; @property (nonatomic ,assign) os_unfair_lock unfairLock; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.count = 0; self.unfairLock = OS_UNFAIR_LOCK_INIT; // 初始化锁 } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (int i = 0; i<10; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ os_unfair_lock_lock(&_unfairLock); // 加锁 self.count ++; NSLog(@"%d",self.count); os_unfair_lock_unlock(&_unfairLock); // 解锁 }); } } @end
NSLock - 互斥锁
NSLock - Foundation框架内部的
加载全部内容