IMG_5742.JPG

锁的种类

  • 自旋锁、互斥锁、读写锁
  • 产生死锁的条件
    • 互斥条件
    • 不可剥夺条件
    • 请求和保持条件
    • 循环等待条件

      @synchronized

  1. 参数 -> self -> nil,传入nil时什么也没做
  2. @synchronized 代码块 -> 是什么
  3. 枷锁的效果
  4. 递归可重入
  5. 结构如何

    举例

  • 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;
} … }

  1. <a name="AOO9W"></a>
  2. ### 底层源码
  3. <a name="K35pP"></a>
  4. #### objc_sync_enter
  5. - **参数为nil时,什么也没做,dose nothing**
  6. - **通过id2data获取data(将id类型的obj封装data),然后进行lock**
  7. - **※SyncData* data = id2data(obj, ACQUIRE)**
  8. ```cpp
  9. int objc_sync_enter(id obj)
  10. {
  11. int result = OBJC_SYNC_SUCCESS;
  12. if (obj) {
  13. SyncData* data = id2data(obj, ACQUIRE);
  14. ASSERT(data);
  15. data->mutex.lock();
  16. } else {
  17. // @synchronized(nil) does nothing
  18. if (DebugNilSync) {
  19. _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
  20. }
  21. objc_sync_nil();
  22. }
  23. return result;
  24. }

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) {

    1. SyncData* data = id2data(obj, RELEASE);
    2. if (!data) {
    3. result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
    4. } else {
    5. bool okay = data->mutex.tryUnlock();
    6. if (!okay) {
    7. result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
    8. }
    9. }

    } else {

    1. // @synchronized(nil) does nothing

    }

  1. return result;

}

  1. <a name="DdmIw"></a>
  2. #### SyncData
  3. - **SyncData底层是一个结构体,单链表**
  4. - **struct SyncData* nextData -> 相同的数据类型,本质一个单链表**
  5. - **DisguisedPtr<objc_object> object -> 类似**[**关联对象**](https://www.yuque.com/raindykukude/fwbpv1/nn2o70#IUewU)**中对object的封装,计算方便,无符号长整型 -> value**
  6. - **threadCount -> 代表了可多线程,多少线程对同一对象进行加锁**
  7. - **mutex -> 递归锁**
  8. ```cpp
  9. typedef struct alignas(CacheLineSize) SyncData {
  10. struct SyncData* nextData;
  11. DisguisedPtr<objc_object> object;
  12. int32_t threadCount; // number of THREADS using this block
  13. recursive_mutex_t mutex;
  14. } SyncData;

未命名文件 (27).jpg

id2data

  • #if SUPPORT_DIRECT_THREAD_KEYS 判断是否支持TLS(局部线程存储,iOS多线程),如果支持则进行栈存储,否则进行cache存储

    1. static SyncData* id2data(id object, enum usage why)
    2. {
    3. // 从hash表中获取object对应的锁,防止对线程同时操作
    4. spinlock_t *lockp = &LOCK_FOR_OBJ(object); //宏定义 获取sDataLists的lockp
    5. // 从hash表中获取object对应的SyncData的地址
    6. SyncData **listp = &LIST_FOR_OBJ(object); //宏定义 获取sDataLists的data
    7. SyncData* result = NULL;
    8. #if SUPPORT_DIRECT_THREAD_KEYS //是否支持TLS
    9. // Check per-thread single-entry fast cache for matching object
    10. bool fastCacheOccupied = NO;
    11. // 支持TLS时,从线程的局部存储获取data
    12. SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    13. if (data) {
    14. ...
    15. }
    16. #endif
    17. // 从线程缓存中查找
    18. // Check per-thread cache of already-owned locks for matching object
    19. SyncCache *cache = fetch_cache(NO);
    20. if (cache) {
    21. ...
    22. }
    23. ...
    24. }
  • TLS存储与cache存储代码类似,以TLS为例,首先会判断传入的对象与缓存对象是否一致,如果一致才进行lockCount++或者lockCount—操作,然后更新当前缓存

  • 当lockCount为0时,OSAtomicDecrement32Barrier(&result->threadCount),表示当前线程空间已经解锁

    1. bool fastCacheOccupied = NO;
    2. SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    3. if (data) {
    4. fastCacheOccupied = YES;
    5. if (data->object == object) {
    6. // Found a match in fast cache.
    7. uintptr_t lockCount;
    8. result = data;
    9. lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
    10. if (result->threadCount <= 0 || lockCount <= 0) {
    11. _objc_fatal("id2data fastcache is buggy");
    12. }
    13. switch(why) {
    14. case ACQUIRE: {
    15. lockCount++;
    16. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
    17. break;
    18. }
    19. case RELEASE:
    20. lockCount--;
    21. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
    22. if (lockCount == 0) {
    23. // remove from fast cache
    24. tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
    25. // atomic because may collide with concurrent ACQUIRE
    26. OSAtomicDecrement32Barrier(&result->threadCount);
    27. }
    28. break;
    29. case CHECK:
    30. // do nothing
    31. break;
    32. }
    33. return result;
    34. }
    35. }
  • TLS及Cache都没有缓存时,开辟一个新的SyncData并添加到表中,只有一个syncData,所以threadCount为1

  • result->nextData = listp,listp = result,头插法,每次新建的SyncData插入在头部,然后listp指向头部
    1. static SyncData* id2data(id object, enum usage why)
    2. {
    3. ...
    4. // Allocate a new SyncData and add to list.
    5. // XXX allocating memory with a global lock held is bad practice,
    6. // might be worth releasing the lock, allocating, and searching again.
    7. // But since we never free these guys we won't be stuck in allocation very often.
    8. posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
    9. result->object = (objc_object *)object;
    10. result->threadCount = 1;
    11. new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
    12. result->nextData = *listp;
    13. *listp = result;
    14. ...
    15. }

    整体流程

  1. 首先如果支持线程栈存储查找,就从TLS(线程局部存储)中查找SyncData,走相应的流程
  2. 如果不支持TLS,则在T线程缓存中查找,如果缓存中有走缓存中的流程
  3. 缓存中没有则判断哈希表中是否有存储对应的SyncData,如果SyncData存在则进行对线程操作同一对象的流程
  4. 如果以上都没有,则表示第一次进来,创建SyncData

    sDataLists

  • sDataLists是一个数组,存储了全局静态变量StripedMap,全局的Hash表,在真机情况array个数是8,其它环境64个
  • StripedMap是一个模板,根据key(void *)hash取value(hashMap)
  • SyncList,是一个结构体,由SyncData和spinlock_t构成
  • SyncCache,结构体,每个线程缓存对应一个SyncCache.SyncCacheItem表示每个对象锁的信息。

    • SyncCacheItem,结构体,包含了SyncData和lockCount当前线程当前对象锁的次数
      1. static StripedMap<SyncList> sDataLists
      tls查找流程
  • 首先在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可多线程

未命名文件 (4).jpg

  • 线程空间相互独立,可以对同一对象加锁,threadCount代表了对个线程对同一对象的加锁

未命名文件 (29).jpg

总结

  • @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,方便存储和释放


头插法图解
未命名文件 (28).jpg