锁的种类
- xcrun编译main.m得到.cpp文件,xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
精简后流程为
- id _sync_obj = (id)appDelegateClassName
- objc_sync_enter(_sync_obj)
- objc_sync_exit(_sync_obj)
@synchronized == objc_sync_enter(obj)和objc_sync_exit (obj) ```cpp int main(int argc, char argv[]) { NSString appDelegateClassName; @autoreleasepool { // Setup code that might create autoreleased objects goes here. appDelegateClassName = NSStringFromClass([AppDelegate class]); @synchronized (appDelegateClassName) {
} } return UIApplicationMain(argc, argv, nil, appDelegateClassName); }
appDelegateClassName = NSStringFromClass(((Class ()(id, SEL))(void )objc_msgSend)((id)objc_getClass(“AppDelegate”), sel_registerName(“class”)));
{
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;
}
…
}
<a name="AOO9W"></a>
### 底层源码
<a name="K35pP"></a>
#### objc_sync_enter
- **参数为nil时,什么也没做,dose nothing**
- **通过id2data获取data(将id类型的obj封装data),然后进行lock**
- **※SyncData* data = id2data(obj, ACQUIRE)**
```cpp
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");
}
objc_sync_nil();
}
return result;
}
objc_sync_exit
- 通过id2data获取data(将obj封装data),然后进行unlock
重点就在SyncData数据结构及id2data方法的调用,与objc_sync_enter不同的是,这里参数传递了RELEASE ```cpp int objc_sync_exit(id obj) { int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
<a name="DdmIw"></a>
#### SyncData
- **SyncData底层是一个结构体,单链表**
- **struct SyncData* nextData -> 相同的数据类型,本质一个单链表**
- **DisguisedPtr<objc_object> object -> 类似**[**关联对象**](https://www.yuque.com/raindykukude/fwbpv1/nn2o70#IUewU)**中对object的封装,计算方便,无符号长整型 -> value**
- **threadCount -> 代表了可多线程,多少线程对同一对象进行加锁**
- **mutex -> 递归锁**
```cpp
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;
id2data
#if SUPPORT_DIRECT_THREAD_KEYS 判断是否支持TLS(局部线程存储,iOS多线程),如果支持则进行栈存储,否则进行cache存储
static SyncData* id2data(id object, enum usage why)
{
// 从hash表中获取object对应的锁,防止对线程同时操作
spinlock_t *lockp = &LOCK_FOR_OBJ(object); //宏定义 获取sDataLists的lockp
// 从hash表中获取object对应的SyncData的地址
SyncData **listp = &LIST_FOR_OBJ(object); //宏定义 获取sDataLists的data
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS //是否支持TLS
// Check per-thread single-entry fast cache for matching object
bool fastCacheOccupied = NO;
// 支持TLS时,从线程的局部存储获取data
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
...
}
#endif
// 从线程缓存中查找
// Check per-thread cache of already-owned locks for matching object
SyncCache *cache = fetch_cache(NO);
if (cache) {
...
}
...
}
TLS存储与cache存储代码类似,以TLS为例,首先会判断传入的对象与缓存对象是否一致,如果一致才进行lockCount++或者lockCount—操作,然后更新当前缓存
当lockCount为0时,OSAtomicDecrement32Barrier(&result->threadCount),表示当前线程空间已经解锁
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
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) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
TLS及Cache都没有缓存时,开辟一个新的SyncData并添加到表中,只有一个syncData,所以threadCount为1
- result->nextData = listp,listp = result,头插法,每次新建的SyncData插入在头部,然后listp指向头部
static SyncData* id2data(id object, enum usage why)
{
...
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
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;
...
}
整体流程
- 首先如果支持线程栈存储查找,就从TLS(线程局部存储)中查找SyncData,走相应的流程
- 如果不支持TLS,则在T线程缓存中查找,如果缓存中有走缓存中的流程
- 缓存中没有则判断哈希表中是否有存储对应的SyncData,如果SyncData存在则进行对线程操作同一对象的流程
- 如果以上都没有,则表示第一次进来,创建SyncData
sDataLists
- sDataLists是一个数组,存储了全局静态变量StripedMap,全局的Hash表,在真机情况array个数是8,其它环境64个
- StripedMap是一个模板,根据key(void *)hash取value(hashMap)
- SyncList,是一个结构体,由SyncData和spinlock_t构成
SyncCache,结构体,每个线程缓存对应一个SyncCache.SyncCacheItem表示每个对象锁的信息。
- SyncCacheItem,结构体,包含了SyncData和lockCount当前线程当前对象锁的次数
tls查找流程static StripedMap<SyncList> sDataLists
- SyncCacheItem,结构体,包含了SyncData和lockCount当前线程当前对象锁的次数
首先在tls查到data,如果data有值fastCacheOccupied = YES
- 如果data->object == object表示加锁的是同一个对象,此时把在tls中查找的data赋值给result
- 如果why是ACQUIRE表示加锁,此时lockCount++,并把lockCount更新到tls中
- 如果why是RELEASE表示解锁,此时lockCount—,并把lockCount更新到tls中,如果lockCount == 0表示当前线程中没有加锁的对象或者已经全部解锁。此时threadCount减1,返回result
如果data->object和object不是同一个对象则进行线程缓存查找流程
哈希函数
哈希函数通过下标获取到对应value,当出现冲突时,可以进行再hash,还可以进行拉链法,例如下图中的syncData
- 可递归性 -> @synchronized锁同一个对象时,形成了拉链的数据结构,其key都是同一个对象
- 在cache时,首先判断当前对象锁了多少次,进行lockCount++或lockCount—,当lockCount为0时,表示当前线程对对象的加锁没有了,但其它线程对该对象可能有加锁,所有@synchronized可多线程
- 线程空间相互独立,可以对同一对象加锁,threadCount代表了对个线程对同一对象的加锁
总结
- @synchronized,可重入多线程可递归。threadCount表示多线程,lockCount表示被一个线程锁多少次。
- TLS保障threadCount,多少条线程对这个对象加锁 -> 同一线程走到了TLS这里
- lock++ 记录进来多少次
- sync是全局的Hash表,采用拉链法syncData
- sDataList arry 存储的是Synclist(objc)
- objc_sync_enter/exit 对称存在,封装的是递归锁
- 两种存储:TLS、Cache
- syncData 第一次,进行头插法创建链表结构,标记threadCount为1
- 判断是不是同一个对象
- TLS->lock++
- TLS找不到sync,threadCount++
- lock解锁,threadCount—
注意:锁的对象不要为空,另外锁对象的生命周期尽量保持全局性,一般传入self,方便存储和释放
头插法图解