当前位置: 代码迷 >> 综合 >> iOS底层原理篇(十六) ---- 自旋锁 atomic
  详细解决方案

iOS底层原理篇(十六) ---- 自旋锁 atomic

热度:65   发布时间:2024-02-07 07:22:38.0

1.概念

自旋锁:它是为实现保护共享资源而提出的一种锁机制。
自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。
无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。
调度机制上略有不同:
互斥锁:如果资源已经被占用,资源申请者只能进入睡眠状态。
自旋锁:不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

可能存在两个问题:
试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。在递归程序中使用自旋锁应遵守下列策略:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环。
过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了. 因此,一般自旋锁实现会有一个参数限定最多持续尝试次数. 超出后, 自旋锁放弃当前time slice. 等下一次机会。

2.atomic原理

//属性
@property (atomic ,strong) NSString *name;
//对name赋值
self.name = @"iOS";
  • 这里会调用setter方法,通过汇编,添加符号断点-[ViewController setName:],汇编我们可以看到调用了objc_setProperty_atomic方法:
    objc_setProperty_atomic
  • 既然调用的是objc_setProperty_atomic方法,那么就可以去objc源码去看了:
	//原子性操作:void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset){	//其实这里之前就已经看过了,setter底层都会调用reallySetProperty,工厂模式嘛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);}//上面都会调用reallySetProperty,只有第五个参数传递的值不一致!!!!看下面的reallySetProperty方法实现,第五个参数就是 bool atomic/**self:隐含参数,对象消息接收者_cmd:隐含参数,SEL方法编号newValue:需要赋值的传入offset:属性所在指针的偏移量atomic:是否是原子操作copy:是否是浅拷贝mutableCopy:是否是深拷贝*/static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool  , 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 {//原子性走这里/**StripedMap<spinlock_t> PropertyLocks;using spinlock_t = mutex_tt<LOCKDEBUG>;template <bool Debug>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);}......}*///先获取了spinlock_t锁spinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();//加锁//新旧值替换oldValue = *slot;*slot = newValue;//解锁 slotlock.unlock();}//释放旧值objc_release(oldValue);}
  • 看源码可以知道,非原子性时reallySetProperty传入false,原子判断时,会直接记录旧值,替换为新值,释放旧值;如果是原子性,传入的值是true,就会先去获得一把锁,锁住值替换代码,完成后解锁,释放旧值!!!
  • 上面是setter方法,那么再去看看getter方法:
	//atomic原子性传入的bool值id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {if (offset == 0) {return object_getClass(self);}//获取加盐哈希表对应的值id *slot = (id*) ((char*)self + offset);//传入的值为false,表示不是原子性的,直接返回获取到的值if (!atomic) return *slot;//这里表示传入的atomic值为true,与setter一样,//获取一把锁,加锁,获取到值,解锁,最后返回获取的值spinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();id value = objc_retain(*slot);slotlock.unlock();//为了提高性能,我们(安全地)在自旋锁之外自动释放。return objc_autoreleaseReturnValue(value);}
  • 总结一下:atomic本质上是在底层给setter/getter添加了一把锁,来保证线程安全!!!那么就意味着,代码在进入getter/setter时,线程是安全的,但是出了getter/setter时,多线程安全就只能靠程序员自己保证了!!!
  • 举个栗子:
	//atomic修饰@property (atomic, strong) NSArray *atomicArr;- (void)wm_test_atomic {//异步任务 Adispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 100000; i ++) {if (i % 2 == 0) {self.atomicArr = @[@"I", @"Love", @"VN"];} else {self.atomicArr = @[@"I can do five in a row"];}NSLog(@"异步任务 A: %@\n", self.atomicArr);}});//异步任务 Bdispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 100000; i ++) {NSString* str = [self.atomicArr objectAtIndex:1];NSLog(@"异步任务 B ==== %@",str);}});}//这里运行的话会出现数组越界的crash//atomic只是保证了setter/getter的安全,外部多线程操作,只有我们自己保证安全
  • 再举个栗子:
	//atomic修饰@property (atomic ,assign) int num;- (void)wm_test_atomic {//异步任务 Adispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 20000; i ++) {self.num = self.num+1;NSLog(@"异步任务 A:%@------%d",NSThread.currentThread,self.num);}});//异步任务 Bdispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 20000; i ++) {self.num = self.num+1;NSLog(@"异步任务 B:%@------%d",NSThread.currentThread,self.num);}});}//打印结果?

atomic

  • 这里打印结果只到39992,看代码,结果应该是40000,所以,atomic只能保证setter/setter安全!!!我们一定要自己处理多线程安全!
  • atomic会消耗更多的资源,性能很低,比nonatomic慢将近20倍,使用的时候,还是建议使用nonatomic!