@synchronized
是 Objective-C
中提供的一个用来快速加锁操作的关键字,该篇文章就深度分析一下该关键字的实现原理,并从中找出一些使用中的注意实现以及使用缺陷。
一、使用方式
@synchronized
关键字的使用十分简单,如下:
- (void)testSynchronized {
@synchronized (self) {
NSLog(@"call test synchronized");
}
}
仅需一个关键字包围并提供一个用来加锁的变量,就能完成对临界区代码的加锁操作,简直无法更加便利快捷。
那么 @synchronized
是如何实现加锁操作的呢?我们来进行进一步的分析。
二、实现方式
我们可以通过 clang
来将上述代码转换为具体实现源码:
static void _I_CustomObject_testSynchronized(CustomObject * self, SEL _cmd) {
{
id _rethrow = 0;
id _sync_obj = (id)self;
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);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_p3_pyrv2p4j0gn_yqv6994w1ryr0000gn_T_CustomObject_77509d_mi_0);
} 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);}
}
}
从上述转换出的源码来看,该关键字将提供的用来加锁的变量赋值给 _sync_obj
变量,同时调用 objc_sync_enter
方法,在此之后执行临界区代码,执行临界区代码后,由一个 _SYNC_EXIT
结构体类型的保存有 _sync_obj
变量的结构体变量的销毁方法调用 objc_sync_exit
方法;在此之后,若有异常抛出,由一个 _FIN
结构体类型的变量来处理抛出的异常。
由上述流程不难看出,临界区代码是在一对 objc_sync_enter
方法和 objc_sync_exit
方法之间执行的,所以 objc_sync_enter
方法完成的是加锁操作, objc_sync_exit
方法完成的是解锁操作。
这两个方法是如何运行的呢?在开始探索这两个方法实现原理之前,先介绍一下相关的数据结构以及知识。
三、相关数据结构及知识
1. SyncData
typedef struct SyncData {
struct SyncData* nextData; //指向下一个SyncData
DisguisedPtr<objc_object> object; //当前加锁的对象
int32_t threadCount; //使用该对象进行加锁的线程数
recursive_mutex_t mutex; //用于加锁的递归锁
} SyncData;
该数据结构为 @synchronized
实现原理中最基本的数据结构,其中记录了提供的用于加锁的变量,使用该变量加锁的线程数以及与该变量一一对应的一个锁。
2. SyncCacheItem
typedef struct {
SyncData *data; //该缓存条目对应的SyncData
unsigned int lockCount; //该对象在该线程中被加锁的次数
} SyncCacheItem;
该数据结构用来记录某个 SyncData
在某个线程中被加锁的记录,由定义可知,一个 SyncData
可以被多个 SyncCacheItem
持有。
3. SyncCache
typedef struct SyncCache {
unsigned int allocated; //该缓存此时对应的缓存大小
unsigned int used; //该缓存此时对应的已使用缓存大小
SyncCacheItem list[0];