iOS底层原理篇(十三)----synchronize源码分析

今天开始,学习一下iOS的相关锁!

@synchronized

  • 首先来看一个经典的买票案例:
	- (void)viewDidLoad {
    	[super viewDidLoad];
    	self.ticketCount = 20;
   		[self wm_testSaleTicket];
	}

	- (void)wm_testSaleTicket{
    	dispatch_async(dispatch_get_global_queue(0, 0), ^{
       		for (int i = 0; i < 5; i++) {
           		[self saleTicket];
        	}
    	});
    	dispatch_async(dispatch_get_global_queue(0, 0), ^{
        	for (int i = 0; i < 5; i++) {
           		[self saleTicket];
        	}
    	});
    	dispatch_async(dispatch_get_global_queue(0, 0), ^{
        	for (int i = 0; i < 3; i++) {
            	[self saleTicket];
        	}
    	});
    	dispatch_async(dispatch_get_global_queue(0, 0), ^{
        	for (int i = 0; i < 10; i++) {
            	[self saleTicket];
        	}
    	});
	}

	- (void)saleTicket{
   		if (self.ticketCount > 0) {
       		self.ticketCount--;
        	sleep(0.1);
       		NSLog(@"当前余票还剩:%ld张",self.ticketCount);
    	}else{
        	NSLog(@"当前车票已售罄");
    	}
	}
  • 看下打印结果:发生了资源抢夺!
    iOS底层原理篇(十三)----synchronize源码分析_第1张图片
  • 修改一下saleTicket方法实现:
	- (void)saleTicket{
		@synchronized (self) {
    		if (self.ticketCount > 0) {
        		self.ticketCount--;
        		sleep(0.1);
        		NSLog(@"当前余票还剩:%ld张",self.ticketCount);
    		}else{
        		NSLog(@"当前车票已售罄");
    		}
    	}
	}
  • 修改后的打印结果:
    iOS底层原理篇(十三)----synchronize源码分析_第2张图片

  • 为什么加了@synchronize就可以了呢?下面我们就去看看它的底层原理吧!!!

  • 打个断点,打开Debug->Debug Workflow->Always Show Disassembly,我们会看到汇编的代码!!!
    objc_sync_enter
    objc_sync_exit

  • 在汇编的代码中,我们发现两个看着很有感觉的方法!!O(∩_∩)O哈哈~在这段代码中间包裹了一大段代码!我们猜想,这就应该是锁的开始与退出,中间是对逻辑的处理!

  • 我们使用clang去确定一下:我们对main函数做如下处理:

	#import 
	#import "AppDelegate.h"

	int main(int argc, char * argv[]) {
    	NSString * appDelegateClassName;
    	@autoreleasepool {
        	appDelegateClassName = NSStringFromClass([AppDelegate class]);
        	@synchronized (appDelegateClassName) {
        	}
    	}
    	return UIApplicationMain(argc, argv, nil, appDelegateClassName);
	}
  • 然后,使用终端cd到当前文件夹路径,输入clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m命令,会在当前文件夹多一个.cpp结尾的文件,这个是c++文件,我们去最下面看main函数的实现:
	int main(int argc, char * argv[]) {
    	NSString * appDelegateClassName;
    	/* @autoreleasepool */
    	{ __AtAutoreleasePool __autoreleasepool;
        	appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
        	//下面的代码块就是@synchronize生成的c++代码,这里就验证了我们的猜想吧!
        	//objc_sync_enter&_sync_exit
        	{
            	id _rethrow = 0;
            	id _sync_obj = (id)appDelegateClassName;
            	objc_sync_enter(_sync_obj);//进入
            	try {
                	struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
                    ~_SYNC_EXIT() {objc_sync_exit(sync_exit);}//析构函数
                   		id sync_exit;
                	} _sync_exit(_sync_obj);//退出
            	} catch (id e) {_rethrow = e;}//下面是特殊情况的抛出异常
            	{ struct _FIN { _FIN(id reth) : rethrow(reth) {}
                	~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
                	id rethrow;
            	} _fin_force_rethow(_rethrow);}
        	}
    	}
    	return UIApplicationMain(argc, argv, __null, appDelegateClassName);
	}
  • 这里只是通过clang去验证了一下,我们去看流程的话,还是通过汇编去研究!打断点的时候,加一个符号断点objc_sync_enter会知道objc_sync_enter in libobjc.A.dylib,在我们的熟悉的objc源码里!
	//开始同步obj对象。如果需要,分配与'obj'相关的递归互斥量。
	//一旦获得锁,返回OBJC_SYNC_SUCCESS。
	int objc_sync_enter(id obj)
	{
    	int result = OBJC_SYNC_SUCCESS;
    	if (obj) {//重点需要看的代码
        	SyncData* data = id2data(obj, ACQUIRE);
        	assert(data);
        	data->mutex.lock();
    	} else {//加锁的对象不存在,抛出的异常!
        	// @synchronized(nil) does nothing
        	if (DebugNilSync) {
            	_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        	}
        	/**
        	BREAKPOINT_FUNCTION(
    			void objc_sync_nil(void)
			);
        	*/
        	objc_sync_nil();
    	}
    	return result;
	}
	//这里顺便把exit的代码也放在这里!!!
	//结束同步'obj'。
	//成功返回OBJC_SYNC_SUCCESS
	//异常返回OBJC_SYNC_NOT_OWNING_THREAD_ERROR
	int objc_sync_exit(id obj)
	{
    	int result = OBJC_SYNC_SUCCESS;
    	if (obj) {
    		//此处获取SyncData数据对象,传入RELEASE
        	SyncData* data = id2data(obj, RELEASE); 
        	if (!data) {//如果获取的data不存在,则报出异常
            	result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
        	} else {
            	bool okay = data->mutex.tryUnlock();//解锁
            	if (!okay) {
            		//解锁失败报出异常
                	result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            	}
        	}
    	} else {//这里和enter一样,如果obj不存在,什么都不做
        	// @synchronized(nil) does nothing
    	}
    	//加解锁成功返回OBJC_SYNC_SUCCESS
    	return result;
	}
  • 这里enter进入后,obj存在,会获取SyncData类型的数据对象,并且调用其结构体内部的mutex加锁!
	//一般情况下,锁使用还是比较少的
	//只在需要的时候给定一个锁。		
	//又由于锁的使用很少,所以会将锁保存在一个表中!
	typedef struct alignas(CacheLineSize) SyncData {
    	struct SyncData* nextData;//指针节点
    	DisguisedPtr<objc_object> object;
    	int32_t threadCount;  //number of THREADS using this block
    	recursive_mutex_t mutex;//互斥锁
	} SyncData;
  • 看一下重点代码部分
	static SyncData* id2data(id object, enum usage why)
	{
		/***
		取出并行列表中的data和lock,使用并行列表的存储方式,减少不相关对象的资源争用
		#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
		#define LIST_FOR_OBJ(obj) sDataLists[obj].data
		static StripedMap sDataLists;
		StripedMap表,存储的是SyncList结构的数据
		struct SyncList {
    		SyncData *data;
    		spinlock_t lock;
    		constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
		};
		StripedMap表中通过indexForPointer下表老获取
		static unsigned int indexForPointer(const void *p) {
        	uintptr_t addr = reinterpret_cast(p);
        	return ((addr >> 4) ^ (addr >> 9)) % StripeCount; // 哈希函数
    	}
		*/
    	spinlock_t *lockp = &LOCK_FOR_OBJ(object);
    	SyncData **listp = &LIST_FOR_OBJ(object);
    	SyncData* result = NULL;
    
	#if SUPPORT_DIRECT_THREAD_KEYS //有确切的SYNC_DATA_DIRECT_KEY标记时,根据key-value查询速度更快,针对单个线程
    	// Check per-thread single-entry fast cache for matching object
    	// 检查每个线程单条目快速缓存是否匹配对象
    	// tls局部存储空间的表
    	bool fastCacheOccupied = NO;
    	//通过key从表中获取存储节点
    	SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    	if (data) {
        	fastCacheOccupied = YES;
        	if (data->object == object) {
            	// 在快速缓存中找匹配
            	uintptr_t lockCount;
            	result = data;
            	//此处获取被锁的次数,说明可以多次进入,允许递归性
            	lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
            	//快速获取节点有bug抛出异常
            	if (result->threadCount <= 0  ||  lockCount <= 0) {
                	_objc_fatal("id2data fastcache is buggy");
            	}
            	//这里enter传入的why值是ACQUIRE,exit传入的是RELEASE
            	switch(why) {
            		case ACQUIRE: {
                		lockCount++;//标记加加
                		//并且设置节点
                		tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                		break;
            		}
            		case RELEASE:
                		lockCount--;//标记值减减
                		//重新设置节点
                		tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                		if (lockCount == 0) {//如果标记值为0,就从快速缓存中移除
                			//移除标记的SyncCacheItem.data
                    		tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
                    		//对threadCount减量操作
                    		/**
                    		int32_t	OSAtomicDecrement32Barrier( volatile int32_t *__theValue ) {
                    			return OSAtomicAdd32Barrier( -1, __theValue); 
                    		}
                    		*/
                    		OSAtomicDecrement32Barrier(&result->threadCount);
                		}
                		break;
            		case CHECK:
                		// do nothing
                		break;
            	}
            	return result;
        	}
    	}
	#endif
    	// Check per-thread cache of already-owned locks for matching object
    	// 检查已拥有锁的每个线程高速缓存中是否有匹配的对象
    	// 全局的@synchronize锁的表
    	// 这里没有SYNC_DATA_DIRECT_KEY标记,不能通过key-value值快速查询,针对全部线程查找
    	SyncCache *cache = fetch_cache(NO);//先去下面看这个函数的实现
    	//这里cache如果不存在则会走下面的流程了
    	if (cache) {//找到缓存数据,下面开始匹配object
        	unsigned int i;
        	for (i = 0; i < cache->used; i++) {
            	SyncCacheItem *item = &cache->list[i];
            	if (item->data->object != object) continue;//不匹配则继续循环查找
            	//找到匹配object
            	result = item->data;
            	if (result->threadCount <= 0  ||  item->lockCount <= 0) {//抛出异常
                	_objc_fatal("id2data cache is buggy");
            	}
            	//这里逻辑和上面的查找到匹配缓存的逻辑一样
            	switch(why) {
            		case ACQUIRE:
	                	item->lockCount++;
                		break;
            		case RELEASE:
                		item->lockCount--;
                		if (item->lockCount == 0) {
                    		cache->list[i] = cache->list[--cache->used];
                    		OSAtomicDecrement32Barrier(&result->threadCount);
                		}
                		break;
            		case CHECK:
                		// do nothing
                		break;
            	}
            	return result;
        	}
    	}
    	// 线程缓存没有找到任何东西。
		// 自旋锁可以防止多个线程为同一个新对象创建多个锁。
		// 如果我们发现有超过20个或更多不同的锁处于活动状态,我们可以将节点保存在某个哈希表中,但我们现在不这样做。
		// 在以前的写法里面,多线程性能没有那么优越,所以@synchronize(self)中的self会被锁在不同的SyncData里面
		// 这里会进行整个链表结构的搜索,进行偏移到下一个节点nextData
    	lockp->lock();//此处加锁.锁住到*listp = result;这段代码的操作
    	{
        	SyncData* p;
        	SyncData* firstUnused = NULL;
        	for (p = *listp; p != NULL; p = p->nextData) {
            	if ( p->object == object ) {
            		//此处找到了匹配的object,result被赋值,说明是多个节点的存储,那么也就是多线程的操作
                	result = p;
                	//threadCount 增量操作
                	/**
                	int32_t	OSAtomicIncrement32Barrier( volatile int32_t *__theValue )
                	{ 
                		return OSAtomicAdd32Barrier(1, __theValue); 
                	}*/
                	OSAtomicIncrement32Barrier(&result->threadCount);
                	//跳转到done
                	goto done;
            	}
            	if ((firstUnused == NULL) && (p->threadCount == 0))
                	firstUnused = p;
        	}
        	// 没有与当前对象关联的SyncData
        	if ( (why == RELEASE) || (why == CHECK) )
        		//调到done,并且why==RELEASE,返回nil;why == CHECK抛出异常
            	goto done;
            //有一个空的SyncData *p可以使用,那么就去设置object为当前的object;
            //并且threadCount = 1;
        	if ( firstUnused != NULL ) {
            	result = firstUnused;
            	result->object = (objc_object *)object;
            	result->threadCount = 1;
            	goto done;
        	}
    	}
    	//分配一个新的SyncData并添加到列表中。
    	//并对result的object设置为当前的object
    	//threadCount = 1
    	posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
    	result->object = (objc_object *)object;
    	result->threadCount = 1;
    	new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
    	//保存节点及保存到表中
    	result->nextData = *listp;
    	*listp = result;
    
 	done:
    	lockp->unlock();
    	if (result) {
			// 所有的释放、检查和递归获取都由上面的每个线程缓存处理。
			// 只有新来的ACQUIRE才能进来
        	if (why == RELEASE) {//在tls中未找到,但是在其他的SyncData中找到匹配的object,如果是relese进来到这里了,那就返回nil
            	// 某些线程的对象被另一个线程持有时!
            	return nil;
        	}
        	//抛出异常
        	if (why != ACQUIRE) _objc_fatal("id2data is buggy");
        	if (result->object != object) _objc_fatal("id2data is buggy");

	#if SUPPORT_DIRECT_THREAD_KEY
			//有确切的SUPPORT_DIRECT_THREAD_KEYS
        	if (!fastCacheOccupied) {
            	// 保存在快速线程缓存中
            	//直接保存在tls局部缓存空间
            	//标记: SYNC_DATA_DIRECT_KEY & SYNC_COUNT_DIRECT_KEY
            	tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
            	tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
        	} else 
	#endif
        	{
            	// 保存在线程缓存中
            	if (!cache) cache = fetch_cache(YES);
            	cache->list[cache->used].data = result;
            	cache->list[cache->used].lockCount = 1;
            	cache->used++;
        	}
    	}
    	return result;
	}
  • fetch_cache(NO)传入值为NO
	- static SyncCache *fetch_cache(bool create)
	{
    	_objc_pthread_data *data;
    	/**
    	//_objc_fetch_pthread_data:为这个线程获取objc的pthread数据。
    	//如果数据还不存在,并且create是NO,返回NULL。
    	//如果数据还不存在,create是YES,分配内存空间并返回data。
    	_objc_pthread_data *_objc_fetch_pthread_data(bool create)
		{
    		_objc_pthread_data *data;
    		data = (_objc_pthread_data *)tls_get(_objc_pthread_key);
    		if (!data  &&  create) {
        		data = (_objc_pthread_data *)
            		calloc(1, sizeof(_objc_pthread_data));
        		tls_set(_objc_pthread_key, data);
    		}
    		return data;
		}
    	*/
    	data = _objc_fetch_pthread_data(create);
    	//如果data不存在,则返回NULL
    	if (!data) return NULL;
		//data存在,但是cache不存在
    	if (!data->syncCache) {
        	if (!create) {//如果传入的creat是NO,返回NULL,上面方法调用走这里,表示没有获取到缓存
            	return NULL;
        	} else {//如果传入的creat是YES,则会给缓存分配内存空间
            	int count = 4;
            	data->syncCache = (SyncCache *)
                	calloc(1, sizeof(SyncCache) + count*sizeof(SyncCacheItem));
            	data->syncCache->allocated = count;
        	}
    	}

    	// 缓存扩容,确保可以存入新的缓存,避免溢出
    	if (data->syncCache->allocated == data->syncCache->used) {
        	data->syncCache->allocated *= 2;
        	data->syncCache = (SyncCache *)
            	realloc(data->syncCache, sizeof(SyncCache) 
                    	+ data->syncCache->allocated * sizeof(SyncCacheItem));
    	}
    	//返回获取到的缓存,这里我们还返回到id2data函数的已存在锁的表查询代码去!!!
    	return data->syncCache;
	}
  • lockCount的处理保证能够递归调用
  • threadCount保证了线程的等待,只需要系统底层发送消息唤醒线程,不造成死锁现象!

你可能感兴趣的:(iOS底层原理)