天天看点

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

1.概念

自旋锁:它是为实现保护共享资源而提出的一种锁机制。

自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。

无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。

调度机制上略有不同:

互斥锁:如果资源已经被占用,资源申请者只能进入睡眠状态。

自旋锁:不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

可能存在两个问题:

试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。在递归程序中使用自旋锁应遵守下列策略:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环。

过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了. 因此,一般自旋锁实现会有一个参数限定最多持续尝试次数. 超出后, 自旋锁放弃当前time slice. 等下一次机会。

2.atomic原理

//属性
@property (atomic ,strong) NSString *name;
//对name赋值
self.name = @"iOS";
           
  • 这里会调用setter方法,通过汇编,添加符号断点-[ViewController setName:],汇编我们可以看到调用了objc_setProperty_atomic方法:
    iOS底层原理篇(十六) ---- 自旋锁 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 {
    
    	//异步任务 A
    	dispatch_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);
        	}
    	});
    
    	//异步任务 B
    	dispatch_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 {
    	//异步任务 A
    	dispatch_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);
        	}
    	});
    
    	//异步任务 B
    	dispatch_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);
        	}
    	});
	}
	//打印结果?
           
iOS底层原理篇(十六) ---- 自旋锁 atomic
  • 这里打印结果只到39992,看代码,结果应该是40000,所以,atomic只能保证setter/setter安全!!!我们一定要自己处理多线程安全!
  • atomic会消耗更多的资源,性能很低,比nonatomic慢将近20倍,使用的时候,还是建议使用nonatomic!