1. 锁的概述

1.1 锁的归类

  • 自旋锁:线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显示释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的

    • OSSpinLock
  • 互斥锁:是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成

    • NSLock

    • pthread_mutex

    • @synchronized

    • os_unfair_lock

  • 条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行

    • NSCondition

    • NSConditionLock

  • 递归锁:同一个线程可以加锁N次而不会引发死锁

    • NSRecursiveLock

    • pthread_mutex(recursive)

  • 信号量(semaphore):一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥

    • dispatch_semaphore
  • 读写锁:一种特殊的自旋锁。它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作

    • 相比自旋锁而言,能提高并发性。因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。但写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数先关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的

    • 如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者

    • 如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁

其实基本的锁就包括三类:⾃旋锁、互斥锁、读写锁,其他的⽐如条件锁、递归锁、信号量都是上层的封装和实现

  • 互斥锁 = 互斥 + 同步,互斥保证线程安全,当一条线程执行时,其他线程休眠。同步保证执行顺序,多线程串行执行

    • NSLockpthread_mutex@synchronizedNSConditionNSConditionLockNSRecursiveLockpthread_mutex(recursive)os_unfair_lock
  • 自旋锁 = 互斥 + 忙等,例如do...while循环。它的优点在于不会引起调用者睡眠,所以不会进行线程调度,CPU时间片轮转等耗时操作。而缺点是当等待时会消耗大量CPU资源,所以自旋锁不适用较长时间的任务

    • OSSpinLock

1.2 锁的性能

2021iPhone 12真机测试,锁的性能对比图:
image.png

  • 性能从高到低依次排列:OSSpinLock(自旋锁)>os_unfair_lock(自旋锁)>NSCondition(条件锁)>pthread_mutex(互斥锁)>NSLock(互斥锁)>dispatch_semaphore(信号量)>pthread_mutex(recursive)(递归锁)>NSRecursiveLock(递归锁)>@synchronized(互斥锁)>NSConditionLock(条件锁)

1.3 测试方案

循环十万次,进行加锁和解锁操作。通过开始、结束时间,计算各自锁的耗时

  1. int kc_runTimes = 100000;

OSSpinLock

  1. /** OSSpinLock 性能 */
  2. {
  3. OSSpinLock kc_spinlock = OS_SPINLOCK_INIT;
  4. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  5. for (int i=0 ; i < kc_runTimes; i++) {
  6. OSSpinLockLock(&kc_spinlock); //解锁
  7. OSSpinLockUnlock(&kc_spinlock);
  8. }
  9. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  10. KCLog(@"OSSpinLock: %f ms",(kc_endTime - kc_beginTime)*1000);
  11. }

dispatch_semaphore_t

  1. /** dispatch_semaphore_t 性能 */
  2. {
  3. dispatch_semaphore_t kc_sem = dispatch_semaphore_create(1);
  4. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  5. for (int i=0 ; i < kc_runTimes; i++) {
  6. dispatch_semaphore_wait(kc_sem, DISPATCH_TIME_FOREVER);
  7. dispatch_semaphore_signal(kc_sem);
  8. }
  9. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  10. KCLog(@"dispatch_semaphore_t: %f ms",(kc_endTime - kc_beginTime)*1000);
  11. }

os_unfair_lock_lock

  1. /** os_unfair_lock_lock 性能 */
  2. {
  3. os_unfair_lock kc_unfairlock = OS_UNFAIR_LOCK_INIT;
  4. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  5. for (int i=0 ; i < kc_runTimes; i++) {
  6. os_unfair_lock_lock(&kc_unfairlock);
  7. os_unfair_lock_unlock(&kc_unfairlock);
  8. }
  9. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  10. KCLog(@"os_unfair_lock_lock: %f ms",(kc_endTime - kc_beginTime)*1000);
  11. }

pthread_mutex_t

  1. /** pthread_mutex_t 性能 */
  2. {
  3. pthread_mutex_t kc_metext = PTHREAD_MUTEX_INITIALIZER;
  4. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  5. for (int i=0 ; i < kc_runTimes; i++) {
  6. pthread_mutex_lock(&kc_metext);
  7. pthread_mutex_unlock(&kc_metext);
  8. }
  9. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  10. KCLog(@"pthread_mutex_t: %f ms",(kc_endTime - kc_beginTime)*1000);
  11. }

NSLock

  1. /** NSLock 性能 */
  2. {
  3. NSLock *kc_lock = [NSLock new];
  4. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  5. for (int i=0 ; i < kc_runTimes; i++) {
  6. [kc_lock lock];
  7. [kc_lock unlock];
  8. }
  9. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  10. KCLog(@"NSlock: %f ms",(kc_endTime - kc_beginTime)*1000);
  11. }

NSCondition

  1. /** NSCondition 性能 */
  2. {
  3. NSCondition *kc_condition = [NSCondition new];
  4. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  5. for (int i=0 ; i < kc_runTimes; i++) {
  6. [kc_condition lock];
  7. [kc_condition unlock];
  8. }
  9. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  10. KCLog(@"NSCondition: %f ms",(kc_endTime - kc_beginTime)*1000);
  11. }

PTHREAD_MUTEX_RECURSIVE

  1. /** PTHREAD_MUTEX_RECURSIVE 性能 */
  2. {
  3. pthread_mutex_t kc_metext_recurive;
  4. pthread_mutexattr_t attr;
  5. pthread_mutexattr_init (&attr);
  6. pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
  7. pthread_mutex_init (&kc_metext_recurive, &attr);
  8. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  9. for (int i=0 ; i < kc_runTimes; i++) {
  10. pthread_mutex_lock(&kc_metext_recurive);
  11. pthread_mutex_unlock(&kc_metext_recurive);
  12. }
  13. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  14. KCLog(@"PTHREAD_MUTEX_RECURSIVE: %f ms",(kc_endTime - kc_beginTime)*1000);
  15. }

NSRecursiveLock

  1. /** NSRecursiveLock 性能 */
  2. {
  3. NSRecursiveLock *kc_recursiveLock = [NSRecursiveLock new];
  4. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  5. for (int i=0 ; i < kc_runTimes; i++) {
  6. [kc_recursiveLock lock];
  7. [kc_recursiveLock unlock];
  8. }
  9. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  10. KCLog(@"NSRecursiveLock: %f ms",(kc_endTime - kc_beginTime)*1000);
  11. }

NSConditionLock

  1. /** NSConditionLock 性能 */
  2. {
  3. NSConditionLock *kc_conditionLock = [NSConditionLock new];
  4. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  5. for (int i=0 ; i < kc_runTimes; i++) {
  6. [kc_conditionLock lock];
  7. [kc_conditionLock unlock];
  8. }
  9. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  10. KCLog(@"NSConditionLock: %f ms",(kc_endTime - kc_beginTime)*1000);
  11. }

@synchronized

  1. /** @synchronized 性能 */
  2. {
  3. double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
  4. for (int i=0 ; i < kc_runTimes; i++) {
  5. @synchronized(self) {}
  6. }
  7. double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
  8. KCLog(@"@synchronized: %f ms",(kc_endTime - kc_beginTime)*1000);
  9. }

其中@synchronized比之前在老版本中的测试结果快了很多,说明官方对其进行了优化

使用模拟器运行,运行结果和真机略有不同,因为系统底层在真机和模拟器上的处理有一些差异

2. @synchronized

2.1 底层实现

查看@synchronized的底层实现,可以使用clangxcrun生成cpp文件,或者通过调试查看汇编代码

2.1.1 【方案一】查看cpp文件

在程序的main函数入口,增加@synchronized代码

  1. int main(int argc, char * argv[]) {
  2. NSString * appDelegateClassName;
  3. @autoreleasepool {
  4. appDelegateClassName = NSStringFromClass([AppDelegate class]);
  5. @synchronized (appDelegateClassName) {
  6. }
  7. }
  8. return UIApplicationMain(argc, argv, nil, appDelegateClassName);
  9. }

使用xcrun生成cpp文件

  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

打开main.cpp文件,找到main函数的实现
image.png

  • 调用_sync_exit传入_sync_obj,相当于调用结构体的构造函数和析构函数。构造函数中没有代码,而析构函数中调用objc_sync_exit函数,传入的sync_exit等同于_sync_obj

    • 使用objc_sync_enter(_sync_obj)函数,进行加锁

    • 使用objc_sync_exit(_sync_obj)函数,进行解锁

  • 使用try...catch,说明锁的使用有可能出现异常

2.1.2 【方案二】查看汇编代码

在main函数设置断点,使用模拟器运行,查看汇编代码
image.png

  • 同样的效果,objc_sync_enterobjc_sync_exit函数成对出现,分别进行加锁和解锁操作

objc_sync_enter设置符号断点
image.png

  • 来自于libobjc.A.dylib

2.2 源码分析

2.2.1 objc_sync_enter

打开objc4-818.2源码,进入objc_sync_enter函数

  1. int objc_sync_enter(id obj)
  2. {
  3. int result = OBJC_SYNC_SUCCESS;
  4. if (obj) {
  5. SyncData* data = id2data(obj, ACQUIRE);
  6. ASSERT(data);
  7. data->mutex.lock();
  8. } else {
  9. // @synchronized(nil) does nothing
  10. if (DebugNilSync) {
  11. _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
  12. }
  13. objc_sync_nil();
  14. }
  15. return result;
  16. }
  • 如果obj存在,执行id2data函数
  • 否则,执行objc_sync_nil函数

进入objc_sync_nil函数

  1. BREAKPOINT_FUNCTION(
  2. void objc_sync_nil(void)
  3. );

找到BREAKPOINT_FUNCTION的宏定义

  1. # define BREAKPOINT_FUNCTION(prototype) \
  2. OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
  3. prototype { asm(""); }

实际上objc_sync_nil中的代码,相当于传入将void objc_sync_nil(void)传入宏,等同于以下代码:

  1. void objc_sync_nil(void) { asm(""); }
  • 相当于无效代码,不会进行加锁操作

查看objc_sync_nil的汇编代码

  1. libobjc.A.dylib`objc_sync_nil:
  2. -> 0x7fff2018a7a3 <+0>: ret
  • 什么都不处理,直接返回

所以,使用@synchronized时,传入nil,相当于无效代码,不会进行加锁操作

2.2.2 objc_sync_exit

进入objc_sync_exit函数

  1. int objc_sync_exit(id obj)
  2. {
  3. int result = OBJC_SYNC_SUCCESS;
  4. if (obj) {
  5. SyncData* data = id2data(obj, RELEASE);
  6. if (!data) {
  7. result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
  8. } else {
  9. bool okay = data->mutex.tryUnlock();
  10. if (!okay) {
  11. result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
  12. }
  13. }
  14. } else {
  15. // @synchronized(nil) does nothing
  16. }
  17. return result;
  18. }
  • 如果obj存在,调用id2data函数,和objc_sync_enter函数中的逻辑相似
  • 否则,什么都不做

由此可见,objc_sync_enterobjc_sync_exit中的核心代码都是id2data函数,参数传入的ACQUIRERELEASE有所区别,但最终都获取到一个SyncData对象

  • objc_sync_enter中,对SyncData对象中的mutex,调用lock进行加锁

  • objc_sync_exit中,对SyncData对象中的mutex,调用tryUnlock进行解锁

2.2.3 SyncData结构

找到SyncData的结构定义

  1. typedef struct alignas(CacheLineSize) SyncData {
  2. struct SyncData* nextData;
  3. DisguisedPtr<objc_object> object;
  4. int32_t threadCount; // number of THREADS using this block
  5. recursive_mutex_t mutex;
  6. } SyncData;

SyncData结构,属于单向链表

  • nextData指向下一条数据

  • DisguisedPtr<objc_object>用于封装类型

  • threadCount记录多线程操作数

  • recursive_mutex_t递归锁,可以递归使用,但不支持多线程递归

所以,通过SyncData的结构不难看出,@synchronized为递归互斥锁,支持多线程递归使用,比recursive_mutex_t更加强大

2.2.4 id2data

进入id2data函数

  1. static SyncData* id2data(id object, enum usage why)
  2. {
  3. //1、传入object,从哈希表中获取数据
  4. //传入object,从哈希表中获取lock,用于保证分配SyncData代码的线程安全
  5. spinlock_t *lockp = &LOCK_FOR_OBJ(object);
  6. //传入object,从哈希表中获取SyncData的地址,等同于SyncList
  7. SyncData **listp = &LIST_FOR_OBJ(object);
  8. SyncData* result = NULL;
  9. #if SUPPORT_DIRECT_THREAD_KEYS
  10. bool fastCacheOccupied = NO;
  11. //2、在当前线程的tls(线程局部存储)中寻找
  12. SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
  13. if (data) {
  14. fastCacheOccupied = YES;
  15. //SyncData中的对象和传入的对象相同
  16. if (data->object == object) {
  17. //可以进入到这里,应该是同一线程中对同一对象,进行嵌套@synchronized
  18. uintptr_t lockCount;
  19. result = data;
  20. lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
  21. if (result->threadCount <= 0 || lockCount <= 0) {
  22. _objc_fatal("id2data fastcache is buggy");
  23. }
  24. switch(why) {
  25. case ACQUIRE: {
  26. //锁的次数+1
  27. lockCount++;
  28. //存储到tls中
  29. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
  30. break;
  31. }
  32. case RELEASE:
  33. //锁的次数-1
  34. lockCount--;
  35. //存储到tls中
  36. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
  37. if (lockCount == 0) {
  38. // remove from fast cache
  39. //删除tls线程局部存储
  40. tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
  41. //对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁
  42. OSAtomicDecrement32Barrier(&result->threadCount);
  43. }
  44. break;
  45. case CHECK:
  46. // do nothing
  47. break;
  48. }
  49. return result;
  50. }
  51. }
  52. #endif
  53. //3、tls中未找到,在各自线程的缓存中查找
  54. SyncCache *cache = fetch_cache(NO);
  55. if (cache) {
  56. unsigned int i;
  57. //遍历缓存
  58. for (i = 0; i < cache->used; i++) {
  59. SyncCacheItem *item = &cache->list[i];
  60. //item中的对象和传入的对象不一致,跳过
  61. if (item->data->object != object) continue;
  62. result = item->data;
  63. if (result->threadCount <= 0 || item->lockCount <= 0) {
  64. _objc_fatal("id2data cache is buggy");
  65. }
  66. switch(why) {
  67. case ACQUIRE:
  68. //锁的次数+1
  69. item->lockCount++;
  70. break;
  71. case RELEASE:
  72. //锁的次数-1
  73. item->lockCount--;
  74. if (item->lockCount == 0) {
  75. // remove from per-thread cache
  76. //从缓存中删除
  77. cache->list[i] = cache->list[--cache->used];
  78. //对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁
  79. OSAtomicDecrement32Barrier(&result->threadCount);
  80. }
  81. break;
  82. case CHECK:
  83. // do nothing
  84. break;
  85. }
  86. return result;
  87. }
  88. }
  89. //加锁,保证下面分配SyncData代码的线程安全
  90. lockp->lock();
  91. {
  92. SyncData* p;
  93. SyncData* firstUnused = NULL;
  94. //4、遍历SyncList,如果无法遍历,证明当前object第一次进入,需要分配新的SyncData
  95. for (p = *listp; p != NULL; p = p->nextData) {
  96. //遍历如果链表中存在SyncData的object和传入的object相等
  97. if ( p->object == object ) {
  98. //将p赋值给result
  99. result = p;
  100. //对threadCount进行+1
  101. OSAtomicIncrement32Barrier(&result->threadCount);
  102. //跳转至done
  103. goto done;
  104. }
  105. //找到一个未使用的SyncData
  106. if ( (firstUnused == NULL) && (p->threadCount == 0) )
  107. firstUnused = p;
  108. }
  109. //未找到与对象关联的SyncData,如果当前非ACQUIRE逻辑,直接进入done
  110. if ( (why == RELEASE) || (why == CHECK) )
  111. goto done;
  112. //从SyncList中找到未使用的SyncData,进行覆盖
  113. if ( firstUnused != NULL ) {
  114. //赋值给result
  115. result = firstUnused;
  116. result->object = (objc_object *)object;
  117. result->threadCount = 1;
  118. //跳转至done
  119. goto done;
  120. }
  121. }
  122. //5、分配一个新的SyncData并添加到SyncList中
  123. posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
  124. result->object = (objc_object *)object;
  125. result->threadCount = 1;
  126. new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
  127. //使用单链表头插法,新增节点总是插在头部
  128. result->nextData = *listp;
  129. *listp = result;
  130. done:
  131. //解锁,保证上面分配SyncData代码的线程安全
  132. lockp->unlock();
  133. if (result) {
  134. //一些错误处理,应该只有ACQUIRE时,产生新SyncData时进入这里
  135. //所有的RELEASE和CHECK和递归ACQUIRE,都应该由上面的线程缓存处理
  136. if (why == RELEASE) {
  137. return nil;
  138. }
  139. if (why != ACQUIRE) _objc_fatal("id2data is buggy");
  140. if (result->object != object) _objc_fatal("id2data is buggy");
  141. //6、保存到tls线程或者缓存中
  142. #if SUPPORT_DIRECT_THREAD_KEYS
  143. if (!fastCacheOccupied) {
  144. //保存到当前线程的tls中
  145. tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
  146. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
  147. } else
  148. #endif
  149. {
  150. //tls还在占用,保存到缓存
  151. if (!cache) cache = fetch_cache(YES);
  152. cache->list[cache->used].data = result;
  153. cache->list[cache->used].lockCount = 1;
  154. cache->used++;
  155. }
  156. }
  157. return result;
  158. }
  • 【第一步】传入object,从哈希表中获取数据

    • 传入object,从哈希表中获取lock,用于保证分配SyncData代码的线程安全

    • 传入object,从哈希表中获取SyncData的地址,等同于SyncList

  • 【第二步】在当前线程的tls(线程局部存储)中寻找

    • tls中获取SyncData

    • 如果存在,对比SyncData中的对象和传入的对象是否相同

      • 相同,根据传入的类型,对lockCount进行++--,更新tls。如果是RELEASE操作,对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁。完成以上逻辑,直接返回result

      • 不同,进入【第三步】

    • 不存在,进入【第三步】

  • 【第三步】tls中未找到,在各自线程的缓存中查找

    • 调用fetch_cache函数,获取SyncCache

    • 如果存在,遍历缓存,对比item中的对象和传入的对象是否相同

      • 相同,根据传入的类型,对lockCount进行++--,更新缓存。如果是RELEASE操作,对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁。完成以上逻辑,直接返回result

      • 遍历结束,未找到相同对象,进入【第四步】

    • 不存在缓存,进入【第四步】

  • 【第四步】遍历SyncList找到SyncData,相当于在所有线程中寻址

    • 对比SyncData中的对象和传入的对象是否相同,同时在遍历过程中,找到未使用的SyncData

      • 相同,对对threadCount进行+1,进入【第六步】

      • 遍历结束,未找到相同对象,查看未使用的SyncData是否存在

        • 存在,将未使用的SyncData中的object覆盖,threadCount重置为1,进入【第六步】

        • 不存在,需要分配新的SyncData,进入【第五步】

    • 如果无法遍历,证明当前object第一次进入,需要分配新的SyncData,进入【第五步】

  • 【第五步】分配一个新的SyncData并添加到SyncList

    • object赋值,threadCount初始化为1

    • 使用单链表头插法,新增节点总是插在头部

    • 进入【第七步】

  • 【第六步】将SyncData保存到tls线程或者缓存中

    • 判断fastCacheOccupied,如果tls中存在SyncDatafastCacheOccupied为真

      • 如果为真,说明tls还在占用,保存到缓存

      • 如果为假,保存到当前线程的tls

通过源码分析,同线程中的lockCount,表示@synchronized可递归使用。而SyncData中的threadCount,表示@synchronized可在多线程中使用


哈希表的结构

  1. #define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
  2. #define LIST_FOR_OBJ(obj) sDataLists[obj].data
  3. static StripedMap<SyncList> sDataLists;
  • lockdata都取自sDataLists,类型为StripedMap,使用static修饰,系统中只存在一份

来到StripedMap的定义

  1. template<typename T>
  2. class StripedMap {
  3. #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
  4. enum { StripeCount = 8 };
  5. #else
  6. enum { StripeCount = 64 };
  7. #endif
  8. ...
  9. }
  • StripedMap是哈希表结构,真机预留8个空间,而模拟器上预留64个空间。所以之前测试锁的性能,使用真机和模拟器运行,结果略有不同

存储到链表的情况,当不同对象生成相同哈希,也就是出现哈希冲突的情况,会直接存储在链表中

另一种情况,以模拟器为例,当StripedMap64个位置都插满后,并没有扩容操作。此时出现新对象,一定会出现哈希冲突,这时会将SyncData插入到链表中

从代码逻辑上看,会进入【第四步】遍历SyncList,但因为SyncData中的对象和传入的对象不同,会在链表中找未使用的SyncData覆盖。如果找不到未使用的SyncData,会分配一个新的SyncData并添加到SyncList
image.png

2.3 TLS线程相关解释

线程局部存储(Thread Local Storage,TLS):是操作系统为线程单独提供的私有空间,通常只有有限的容量

Linux系统下通过pthread库中的API使用:

  • pthread_key_create()

  • pthread_getspecific()

  • pthread_setspecific()

  • pthread_key_delete()

2.4 注意事项

  • @synchronized为递归互斥锁,lockCount表示可递归使用,threadCount表示可在多线程中使用

  • 使用@synchronized时,不能传入nil,使用nil锁的功能无法生效

  • 在日常开发中,经常会传入self,它的好处可以保证生命周期同步,对象不会提前释放

  • 不能使用非OC对象作为加锁对象,因为其object的参数为id类型

  • 底层的缓存和链表都使用循环遍历查找,所以性能偏低。但开发中使用方便简单,并且不用解锁,所以使用频率较高

3. pthread_mutex

Posix Thread中定义有⼀套专⻔⽤于线程同步的mutex函数

mutex:⽤于保证在任何时刻,都只能有⼀个线程访问该对象。当获取锁操作失败时,线程会进⼊睡眠,等待锁释放时被唤醒

API

  1. //导入头文件
  2. #import <pthread/pthread.h>
  3. //锁的声明
  4. pthread_mutex_t _lock;
  5. //锁的初始化
  6. pthread_mutex_init(&_lock, NULL);
  7. //加锁
  8. pthread_mutex_lock(&_lock);
  9. //解锁
  10. pthread_mutex_unlock(&_lock);
  11. //锁的释放
  12. pthread_mutex_destroy(&_lock);

4. NSLock

NSLock的底层对pthread_mutex进行封装,同样是一把互斥锁。但它属于非递归互斥锁,所有不能进行递归使用

API

  1. //锁的初始化
  2. NSLock *lock = [[NSLock alloc] init];
  3. //加锁
  4. [lock lock];
  5. //解锁
  6. [lock unlock];

4.1 源码分析

NSLock的源码在Foundation框架中
image.png

由于OCFoundation框架未能开源,我们通过SwiftFoundation源码代替

打开swift-corelibs-foundation-master项目,找到NSLock的实现

NSLock遵循NSLocking协议

  1. public protocol NSLocking {
  2. func lock()
  3. func unlock()
  4. }

NSLock的初始化、销毁、加锁、解锁功能,都是对pthread_mutex进行二次封装
image.png
image.png

  • NSLock的初始化方法中,封装了pthread_mutex的初始化
  • 使用NSLock必须调用它的init方法
  • 通过pthread_cond_broadcast广播,唤醒在锁中等待的所有线程

4.2 缺陷

NSLock属于非递归互斥锁,所有不能进行递归使用

案例:

  1. - (void)lg_testRecursive{
  2. NSLock *lock = [[NSLock alloc] init];
  3. for (int i=0; i<10; i++) {
  4. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  5. static void (^testMethod)(int);
  6. testMethod = ^(int value){
  7. if (value > 0) {
  8. NSLog(@"current value = %d",value);
  9. testMethod(value - 1);
  10. }
  11. };
  12. [lock lock];
  13. testMethod(10);
  14. [lock unlock];
  15. });
  16. }
  17. }
  • 在异步函数的代码中,使用NSLock进行加锁和解锁,可以保证线程安全,从10~1顺序输出,循环打印10

block方法中进行加锁,同样可以保证线程安全。但NSLock属于非递归锁,遇到递归场景,在没有unlock的情况下,再次执行lock,造成死锁
image.png

  • 后面的结果无法继续打印

5. NSRecursiveLock

NSRecursiveLock也是对pthread_mutex进行封装,属于递归锁。它可以允许同一线程多次加锁,而不会造成死锁

  • NSLock相比,它可以在递归场景中使用
  • 但它没有@synchronized强大,因为它不支持在多线程中递归加锁

API

  1. //锁的初始化
  2. NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
  3. //加锁
  4. [recursiveLock lock];
  5. //解锁
  6. [recursiveLock unlock];

5.1 源码分析

找到NSRecursiveLock的实现,同样遵循了NSLocking协议。NSRecursiveLock的初始化、销毁、加锁、解锁功能,都是对pthread_mutex进行二次封装
image.png
image.png

NSRecursiveLock的实现和NSLock非常相似,它们最大的区别在于初始化,NSRecursiveLockpthread_mutex设置PTHREAD_MUTEX_RECURSIVE标示,故此NSRecursiveLock是一把递归互斥锁

  1. pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))

5.2 缺陷

NSRecursiveLock会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用

案例:
image.png

  • 在单一线程中正常打印出10~1后,多线程递归执行报错

6. NSCondition

NSCondition属于条件锁,使用的方式和信号量相似。当线程满足需求后才会继续执行,否则会阻塞线程,使其进入休眠状态

NSCondition的对象实际上作为⼀个锁和⼀个线程检查器

  • 锁主要为了当检测条件时保护数据源,执⾏条件引发的任务
  • 线程检查器主要是根据条件决定是否继续运⾏线程,即线程是否被阻塞

API

  1. //⽤于多线程同时访问、修改同⼀个数据源
  2. //保证在同⼀时间内数据源只被访问、修改⼀次
  3. //其他线程的命令需要在lock外等待,直到unlock,才可访问
  4. [condition lock];
  5. //与lock同时使⽤
  6. [condition unlock];
  7. //使当前线程处于等待状态
  8. [condition wait];
  9. //CPU发信号告诉线程不⽤等待,可以继续执⾏
  10. [condition signal];

6.1 使用

生产消费者的模型案例:

  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @property (nonatomic, assign) NSUInteger ticketCount;
  4. @property (nonatomic, strong) NSCondition *testCondition;
  5. @end
  6. @implementation ViewController
  7. - (void)viewDidLoad {
  8. [super viewDidLoad];
  9. [self lg_testConditon];
  10. }
  11. #pragma mark -- NSCondition
  12. - (void)lg_testConditon{
  13. _testCondition = [[NSCondition alloc] init];
  14. //创建生产-消费者
  15. for (int i = 0; i < 50; i++) {
  16. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  17. [self lg_consumer];
  18. });
  19. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  20. [self lg_consumer];
  21. });
  22. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  23. [self lg_producer];
  24. });
  25. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  26. [self lg_producer];
  27. });
  28. }
  29. }
  30. - (void)lg_producer{
  31. //操作的多线程影响
  32. [_testCondition lock];
  33. self.ticketCount = self.ticketCount + 1;
  34. NSLog(@"生产一个 现有 count %zd",self.ticketCount);
  35. //信号
  36. [_testCondition signal];
  37. [_testCondition unlock];
  38. }
  39. - (void)lg_consumer{
  40. //操作的多线程影响
  41. [_testCondition lock];
  42. while (self.ticketCount == 0) {
  43. NSLog(@"等待 count %zd",self.ticketCount);
  44. [_testCondition wait];
  45. }
  46. //注意消费行为,要在等待条件判断之后
  47. self.ticketCount -= 1;
  48. NSLog(@"消费一个 还剩 count %zd",self.ticketCount);
  49. [_testCondition unlock];
  50. }
  51. @end

生产者和消费者都进行了加锁处理,保证线程安全

当消费者发现库存为0的时候,线程等待

当生产者增加库存后,发送信号。消费者收到信号,之前等待的线程继续执行

消费者判断库存为0,使用while循环,而不是if判断

  • 使用if判断,唤醒线程后会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码

  • 使用while循环,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait

6.2 源码分析

找到NSCondition的实现
image.png
image.png
image.png

  • 底层采用pthread_mutex + pthread_cond实现

    • pthread_mutex:对互斥锁进行封装

    • pthread_cond:用来控制条件变量的执行情况

  • wait:操作会阻塞线程,使其进入休眠状态

  • signal:操作唤醒一个正在休眠等待的线程,使其继续执行wait之后的代码块

  • broadcast:唤醒在锁中等待的所有线程

7. NSConditionLock

NSConditionLock是一把条件锁,⼀个线程获得锁,其他线程⼀定等待。它的内部对NSCondition又做了一层封装,自带条件探测,能够更简单灵活的使用

NSCondition相比:

  • 相同点:

    • 都是互斥锁

    • 通过条件变量来控制加锁、释放锁,从而达到阻塞线程、唤醒线程的目的

  • 不同点:

    • NSCondition是基于pthread_mutex的封装,而NSConditionLock是基于NSCondition又做了一层封装

    • NSCondition需要手动让线程进入等待状态阻塞线程、释放信号唤醒线程,而NSConditionLock只需要外部传入一个值,就会依据这个值进行自动判断,决定阻塞线程还是唤醒线程

API:

⽆条件锁:

  1. [conditionLock lock];
  2. [conditionLock unlock];
  • 表示condition期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition) ,那它能执⾏此⾏以下代码
  • 如果已经有其他线程获得锁(可能是条件锁,或者⽆条件锁),则等待,直⾄其他线程解锁

条件锁:

  1. [conditionLock lockWhenCondition:A条件];
  • 表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待
  • 如果内部的condition等于A条件,并且没有其他线程获得该锁,则进⼊代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直⾄它解锁
  • 所谓的A条件就是整数,内部通过整数⽐较条件

释放条件锁

  1. [conditionLock unlockWithCondition:A条件];
  • 表示释放锁,同时把内部的condition设置为A条件

条件锁 + 超时时间:

  1. return = [conditionLock lockWhenCondition:A条件 beforeDate:A时间];
  • 表示如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函数的⽬的在于可以实现两种状态下的处理

7.1 使用

案例:

  1. - (void)lg_testConditonLock{
  2. NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
  3. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  4. [conditionLock lockWhenCondition:1];
  5. NSLog(@"线程1");
  6. [conditionLock unlockWithCondition:0];
  7. });
  8. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
  9. [conditionLock lockWhenCondition:2];
  10. NSLog(@"线程2");
  11. [conditionLock unlockWithCondition:1];
  12. });
  13. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  14. [conditionLock lock];
  15. NSLog(@"线程3");
  16. [conditionLock unlock];
  17. });
  18. }
  • 案例的执行结果,大概率为线程3、线程2、线程1

NSConditionLock初始化,设置的条件为2。按照线程优先级顺序:

  • 线程1优先级最高,但不符合条件,代码块无法执行,进入等待状态

  • 线程3默认优先级,同时属于无条件锁,可以执行代码块

  • 线程2优先级最低,但符合条件,也可以执行代码块

所以,大概率线程3会在线程2之前打印,而线程1必须等待线程2执行完,释放条件锁之后才能执行

7.2 源码分析

初始化:
image.png

  • NSCondition和条件value,作为NSConditionLock的成员变量
  • 初始化时将条件赋值value

无条件锁:

  1. open func lock() {
  2. let _ = lock(before: Date.distantFuture)
  3. }
  4. open func lock(before limit: Date) -> Bool {
  5. _cond.lock()
  6. while _thread != nil {
  7. if !_cond.wait(until: limit) {
  8. _cond.unlock()
  9. return false
  10. }
  11. }
  12. #if os(Windows)
  13. _thread = GetCurrentThread()
  14. #else
  15. _thread = pthread_self()
  16. #endif
  17. _cond.unlock()
  18. return true
  19. }
  • 调用lock方法,底层调用的是lock(before limit: Date)方法,无条件判断,可直接执行

释放无条件锁:

  1. open func unlock() {
  2. _cond.lock()
  3. #if os(Windows)
  4. _thread = INVALID_HANDLE_VALUE
  5. #else
  6. _thread = nil
  7. #endif
  8. _cond.broadcast()
  9. _cond.unlock()
  10. }
  • 调用unlock方法,实际上处理的是NSCondition

条件锁:

  1. open func lock(whenCondition condition: Int) {
  2. let _ = lock(whenCondition: condition, before: Date.distantFuture)
  3. }
  4. open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
  5. _cond.lock()
  6. while _thread != nil || _value != condition {
  7. if !_cond.wait(until: limit) {
  8. _cond.unlock()
  9. return false
  10. }
  11. }
  12. #if os(Windows)
  13. _thread = GetCurrentThread()
  14. #else
  15. _thread = pthread_self()
  16. #endif
  17. _cond.unlock()
  18. return true
  19. }
  • 内部进行lockunlock处理,保证线程安全
  • 如果条件不一致,调用wait方法进入等待状态
  • 否则,调用pthread_self方法

释放条件锁:

  1. open func unlock(withCondition condition: Int) {
  2. _cond.lock()
  3. #if os(Windows)
  4. _thread = INVALID_HANDLE_VALUE
  5. #else
  6. _thread = nil
  7. #endif
  8. _value = condition
  9. _cond.broadcast()
  10. _cond.unlock()
  11. }
  • 内部进行lockunlock处理,保证线程安全
  • 更新条件value
  • 调用broadcast方法,进行广播,唤醒在锁中等待的所有线程

7.3 汇编分析

7.3.1 initWithCondition:

设置-[NSConditionLock initWithCondition:]符号断点,真机运行项目
image.png

  • 面对复杂的汇编代码,我们可以对所有的跳转代码设置断点,例如:blb等指令,然后读取x0x1寄存器查看消息接收者和方法编号,遇到ret指令,通过x0查看返回值

执行bl 0x188fa9e34指令

  1. (lldb) register read x0
  2. x0 = 0x000000016f7b99c8
  3. (lldb) register read x1
  4. x1 = 0x00000001d0748898
  5. (lldb) po 0x000000016f7b99c8
  6. 6165338568
  7. (lldb) po (SEL)0x00000001d0748898
  8. "init"
  • 调用一个未知对象的init方法

执行bl 0x1893db4fc指令

  1. (lldb) register read x0
  2. x0 = 0x0000000283e72670
  3. (lldb) register read x1
  4. x1 = 0x00000001d0748898
  5. (lldb) po 0x0000000283e72670
  6. <NSConditionLock: 0x283e72670>{condition = 0, name = nil}
  7. (lldb) po (SEL)0x00000001d0748898
  8. "init"
  • 调用NSConditionLock对象的init方法

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000283e72670
  3. (lldb) register read x1
  4. x1 = 0x00000001cffedc93
  5. (lldb) po 0x0000000283e72670
  6. <NSConditionLock: 0x283e72670>{condition = 0, name = nil}
  7. (lldb) po (SEL)0x00000001cffedc93
  8. "zone"
  • 调用NSConditionLock对象的zone方法,开辟内存空间

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x00000001de1d0e20 (void *)0x00000001de1d0e48: NSCondition
  3. (lldb) register read x1
  4. x1 = 0x00000001d0c3d418
  5. (lldb) po (SEL)0x00000001d0c3d418
  6. "allocWithZone:"
  7. (lldb) register read x2
  8. x2 = 0x00000001ded98000 libsystem_malloc.dylib`virtual_default_zone
  • 调用NSCondition类对象的allocWithZone:方法
  • 传入参数:virtual_default_zone

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000280220510
  3. (lldb) register read x1
  4. x1 = 0x00000001d0748898
  5. (lldb) po 0x0000000280220510
  6. <NSCondition: 0x280220510>{name = nil}
  7. (lldb) po (SEL)0x00000001d0748898
  8. "init"
  • 调用NSCondition对象的init方法

image.png

  • 比较x8x9寄存器
  • b.ne:比较结果是不等于(not equal to),执行标号处指令,否则继续执行

输出x8x9寄存器

  1. (lldb) register read x9
  2. x9 = 0x933ba4ad46660065
  3. (lldb) register read x8
  4. x8 = 0x933ba4ad46660065
  • 相等,继续执行

执行ret指令,输出x0寄存器,查看返回值

  1. (lldb) register read x0
  2. x0 = 0x0000000283e72670
  3. (lldb) po 0x0000000283e72670
  4. <NSConditionLock: 0x283e72670>{condition = 2, name = nil}
  • 返回NSConditionLock实例对象

使用x/8g命令,查看实例对象的内存结构

  1. (lldb) x/8g 0x283e72670
  2. 0x283e72670: 0x000021a1de1d0ec1 0x0000000000000000
  3. 0x283e72680: 0x0000000280220510 0x0000000000000000
  4. 0x283e72690: 0x0000000000000002 0x0000000000000000
  5. 0x283e726a0: 0x0000a817e1de26a0 0x00000000006000cc
  6. (lldb) po 0x0000000280220510
  7. <NSCondition: 0x280220510>{name = nil}
  • NSCondition和初始化设置的默认条件,都作为成员变量保存在NSConditionLock的实例对象中
  • 设置的默认条件:0x0000000000000002,即:初始化时传入的2

7.3.2 lockWhenCondition:

设置-[NSConditionLock lockWhenCondition:]符号断点
image.png

输出x0x1x2寄存器

  1. (lldb) register read x0
  2. x0 = 0x0000000283fcbb40
  3. (lldb) register read x1
  4. x1 = 0x00000001d0543629
  5. (lldb) register read x2
  6. x2 = 0x0000000000000001
  7. (lldb) po 0x0000000283fcbb40
  8. <NSConditionLock: 0x283fcbb40>{condition = 2, name = nil}
  9. (lldb) po (SEL)0x00000001d0543629
  10. "lockWhenCondition:"
  • 执行线程1的代码
  • x2:传入的条件值为1

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x00000001de1c7030 (void *)0x00000001de1c7058: NSDate
  3. (lldb) register read x1
  4. x1 = 0x00000001d0f8b6aa
  5. (lldb) po (SEL)0x00000001d0f8b6aa
  6. "distantFuture"
  • 调用NSDatedistantFuture方法

执行b 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x00000002813fc360
  3. (lldb) register read x1
  4. x1 = 0x00000001d070122e
  5. (lldb) po 0x00000002813fc360
  6. <NSConditionLock: 0x2813fc360>{condition = 2, name = nil}
  7. (lldb) po (SEL)0x00000001d070122e
  8. "lockWhenCondition:beforeDate:"
  9. (lldb) register read x2
  10. x2 = 0x0000000000000001
  11. (lldb) register read x3
  12. x3 = 0x00000001d64d7168 CoreFoundation`_NSConstantDateDistantFuture
  • 调用NSConditionLock对象的lockWhenCondition:beforeDate:方法
  • x2:传入的条件值1
  • x3:上一个bl,调用distantFuture方法得到的返回值

7.3.3 lockWhenCondition:beforeDate:

设置-[NSConditionLock lockWhenCondition:beforeDate:]符号断点
image.png

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000280220510
  3. (lldb) register read x1
  4. x1 = 0x00000001d0195618
  5. (lldb) po 0x0000000280220510
  6. <NSCondition: 0x280220510>{name = nil}
  7. (lldb) po (SEL)0x00000001d0195618
  8. "lock"
  • 调用NSCondition对象的lock方法

image.png

  • 比较x8x21寄存器
  • b.eq:比较结果是等于(equal to),执行标号处指令,否则继续执行

输出x8x21寄存器

  1. (lldb) register read x8
  2. x8 = 0x0000000000000002
  3. (lldb) register read x21
  4. x21 = 0x0000000000000001
  • 不相等,继续执行

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000281ad5050
  3. (lldb) register read x1
  4. x1 = 0x00000001d099aac3
  5. (lldb) po 0x0000000281ad5050
  6. <NSCondition: 0x281ad5050>{name = nil}
  7. (lldb) po (SEL)0x00000001d099aac3
  8. "waitUntilDate:"
  • 调用NSCondition对象的waitUntilDate:方法
  • 线程进入等待状态

程序继续执行,此时会切换线程,跳转到线程2lockWhenCondition:方法
image.png

  • x2:传入的条件值为2

程序继续执行,进入线程2lockWhenCondition:beforeDate:方法

  1. (lldb) register read x0
  2. x0 = 0x0000000283e72670
  3. (lldb) register read x1
  4. x1 = 0x00000001d070122e
  5. (lldb) po 0x0000000283e72670
  6. <NSConditionLock: 0x283e72670>{condition = 2, name = nil}
  7. (lldb) po (SEL)0x00000001d070122e
  8. "lockWhenCondition:beforeDate:"
  9. (lldb) register read x2
  10. x2 = 0x0000000000000002
  11. (lldb) register read x3
  12. x3 = 0x00000001d64d7168 CoreFoundation`_NSConstantDateDistantFuture
  • 和之前调用线程1的区别,传入的条件值为2

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000280220510
  3. (lldb) register read x1
  4. x1 = 0x00000001d0195618
  5. (lldb) po 0x0000000280220510
  6. <NSCondition: 0x280220510>{name = nil}
  7. (lldb) po (SEL)0x00000001d0195618
  8. "lock"
  • 调用NSCondition对象的lock方法

image.png

  • 比较x8x21寄存器
  • b.eq:比较结果是等于(equal to),执行标号处指令,否则继续执行

输出x8x21寄存器

  1. (lldb) register read x8
  2. x8 = 0x0000000000000002
  3. (lldb) register read x21
  4. x21 = 0x0000000000000002
  • 相等,执行标号处指令

执行bl 0x188faa140指令

  1. (lldb) register read x0
  2. x1 = 0x0000000000000001
  • 只能看出x0寄存器的值为1,具体执行的代码未知

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000280220510
  3. (lldb) register read x1
  4. x1 = 0x00000001d03bca18
  5. (lldb) po 0x0000000280220510
  6. <NSCondition: 0x280220510>{name = nil}
  7. (lldb) po (SEL)0x00000001d03bca18
  8. "unlock"
  • 调用NSCondition对象的unlock方法

执行ret指令,输出x0寄存器,查看返回值

  1. (lldb) register read x0
  2. x0 = 0x0000000000000001
  • 返回值为1,表示条件符合,代码块执行完成

如果条件不符合,线程进入等待状态,因为超时导致代码块未执行,会进行解锁,然后返回0
image.png

7.3.4 unlockWithCondition:

设置-[NSConditionLock unlockWithCondition:]符号断点
image.png

进入线程2unlockWithCondition:方法

  1. (lldb) register read x2
  2. x2 = 0x0000000000000001
  • 传入的条件值为1

执行bl 0x1893db4fc指令

  1. (lldb) register read x0
  2. x0 = 0x0000000282688330
  3. (lldb) register read x1
  4. x1 = 0x00000001d025a602
  5. (lldb) po 0x0000000282688330
  6. <NSConditionLock: 0x282688330>{condition = 2, name = nil}
  7. (lldb) po (SEL)0x00000001d025a602
  8. "unlockWithCondition:"
  • 调用NSConditionLock对象的unlockWithCondition:方法

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000281ad5050
  3. (lldb) register read x1
  4. x1 = 0x00000001d0195618
  5. (lldb) po 0x0000000281ad5050
  6. <NSCondition: 0x281ad5050>{name = nil}
  7. (lldb) po (SEL)0x00000001d0195618
  8. "lock"
  • 调用NSCondition对象的lock方法

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000281ad5050
  3. (lldb) register read x1
  4. x1 = 0x00000001d004280d
  5. (lldb) po 0x0000000281ad5050
  6. <NSCondition: 0x281ad5050>{name = nil}
  7. (lldb) po (SEL)0x00000001d004280d
  8. "broadcast"
  • 调用NSCondition对象的broadcast方法,进行广播,唤醒在锁中等待的所有线程

执行b 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000281ad5050
  3. (lldb) register read x1
  4. x1 = 0x00000001d03bca18
  5. (lldb) po 0x0000000281ad5050
  6. <NSCondition: 0x281ad5050>{name = nil}
  7. (lldb) po (SEL)0x00000001d03bca18
  8. "unlock"
  • 调用NSCondition对象的unlock方法

使用b指令,表示义无反顾的跳转,不再返回。此方法执行完,线程2的代码执行结束

但是,在线程2中,调用NSCondition对象的broadcast方法,触发广播操作。此时,处于等待状态的线程1将被唤醒,继续线程1的代码执行

7.3.5 唤醒线程1

程序继续执行,此时会切换线程,跳转到线程1lockWhenCondition:方法

此时线程1解除等待状态,继续执行代码
image.png

  • 比较x8x21寄存器
  • b.eq:比较结果是等于(equal to),执行标号处指令,否则继续执行

输出x8x21寄存器

  1. (lldb) register read x8
  2. x8 = 0x0000000000000001
  3. (lldb) register read x21
  4. x21 = 0x0000000000000001
  • 相等,执行标号处指令

执行bl 0x188faa140指令

  1. (lldb) register read x0
  2. x1 = 0x0000000000000001
  • 只能看出x0寄存器的值为1,具体执行的代码未知

执行bl 0x188fa9e28指令

  1. (lldb) register read x0
  2. x0 = 0x0000000280220510
  3. (lldb) register read x1
  4. x1 = 0x00000001d03bca18
  5. (lldb) po 0x0000000280220510
  6. <NSCondition: 0x280220510>{name = nil}
  7. (lldb) po (SEL)0x00000001d03bca18
  8. "unlock"
  • 调用NSCondition对象的unlock方法

执行ret指令,输出x0寄存器,查看返回值

  1. (lldb) register read x0
  2. x0 = 0x0000000000000001
  • 返回值为1

之后会进入线程1unlockWithCondition:方法,释放条件锁

8. 自旋锁

8.1 atomic

atomic是属性的修饰,不是锁

源码中针对atomic加锁的地方都定义为spinlock,也就是自旋锁。但是苹果在iOS10之后弃用OSSpinLock,内部使用os_unfair_lock实现,所以它在底层自带的是一把互斥锁

  1. using spinlock_t = mutex_tt<LOCKDEBUG>;
  2. class mutex_tt : nocopy_t {
  3. os_unfair_lock mLock;
  4. ...
  5. };

使用atomic修饰,只是能保证数据的完整性,单一线程安全,不能保证多线程安全。锁操作导致效率低,不推荐使用

案例:

  1. - (void)lg_test_atomic2{
  2. //Thread A
  3. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  4. for (int i = 0; i < 100000; i ++) {
  5. if (i % 2 == 0) {
  6. self.array = @[@"Hank", @"CC", @"Cooci"];
  7. }
  8. else {
  9. self.array = @[@"Kody"];
  10. }
  11. NSLog(@"Thread A: %@\n", self.array);
  12. }
  13. });
  14. //Thread B
  15. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  16. for (int i = 0; i < 100000; i ++) {
  17. if (self.array.count >= 2) {
  18. NSString* str = [self.array objectAtIndex:1];
  19. }
  20. NSLog(@"Thread B: %@\n",self.array);
  21. }
  22. });
  23. }

线程A中,循环遍历的索引为偶数,数组中的元素一定存在三个

线程B中,如果数组中的元素>= 2,获取第一个元素

如果这两个逻辑在同一线程中执行,一定不会报错。但由于多线程,atomic无法保证多线程安全,所以程序还是会崩溃
image.png

8.2 OSSpinLock

OSSpinLock是一把自旋锁,由于安全性问题,iOS10之后被os_unfair_lock(互斥锁)替代。使用OSSpinLock,会让线程处于忙等待状态。目前OSSpinLock还可以使用,但不推荐

案例:

  1. #import "ViewController.h"
  2. #import <libkern/OSAtomic.h>
  3. #import <os/lock.h>
  4. @interface ViewController (){
  5. OSSpinLock _spinLock;
  6. }
  7. @implementation ViewController
  8. - (void)viewDidLoad {
  9. [super viewDidLoad];
  10. [self lg_testOSSPinLock];
  11. }
  12. #pragma mark -- OSSpinLock
  13. - (void)lg_testOSSPinLock{
  14. _spinLock = OS_SPINLOCK_INIT;
  15. // 线程1
  16. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  17. NSLog(@"线程1 准备上锁,currentThread:%@",[NSThread currentThread]);
  18. OSSpinLockLock(&_spinLock);
  19. NSLog(@"线程1");
  20. sleep(3);
  21. OSSpinLockUnlock(&_spinLock);
  22. NSLog(@"线程1 解锁完成");
  23. });
  24. // 线程2
  25. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  26. NSLog(@"线程2 准备上锁,currentThread:%@",[NSThread currentThread]);
  27. OSSpinLockLock(&_spinLock);
  28. NSLog(@"线程2");
  29. sleep(3);
  30. OSSpinLockUnlock(&_spinLock);
  31. NSLog(@"线程2 解锁完成");
  32. });
  33. }
  34. @end

9. os_unfair_lock

os_unfair_lock是一把互斥锁,使用os_unfair_lock,会让线程处于休眠状态

API

  1. //导入头文件
  2. #import <libkern/OSAtomic.h>
  3. #import <os/lock.h>
  4. //初始化
  5. os_unfair_lock _unfairLock = OS_UNFAIR_LOCK_INIT;
  6. //加锁
  7. os_unfair_lock_lock(&_unfairLock);
  8. //解锁
  9. os_unfair_lock_unlock(&_unfairLock);

10. 读写锁

通常,当读写锁处于读模式锁住状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁⻓期占⽤,⽽等待的写模式锁请求⻓期阻塞

读写锁适合于对数据结构的读次数⽐写次数多得多的情况,因为读模式锁定时可以共享,以写模式锁住时意味着独占,所以读写锁⼜叫共享/独占锁

读写锁要实现以下几点:

  • 多读单写

  • 读和写互斥

  • 写操作之间互斥

  • 不能阻塞正常的任务执行

读写锁的两种实现方式:

  • 使用pthread实现

  • 使用GCD实现

10.1 使用pthread实现

  1. //导入头文件
  2. #include <pthread.h>
  3. //初始化
  4. int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)
  5. //释放
  6. int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
  • 成功返回0, 出错返回错误编号
  • 同互斥量以上,在释放读写锁占⽤的内存之前,需要先通过pthread_rwlock_destroy对读写锁进⾏清理⼯作,释放由init分配的资源
  1. //读锁
  2. int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
  3. //写锁
  4. int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
  5. //释放锁
  6. int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
  • 成功返回0, 出错返回错误编号
  • 三个函数分别实现获取读锁,获取写锁和释放锁的操作.
  • 获取锁的两个函数是阻塞操作

⾮阻塞的获取锁操作:

  1. //读锁
  2. int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
  3. //写锁
  4. int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
  • 成功返回0, 出错返回错误编号

10.2 使用GCD实现

使用GCD创建自定义并发队列

  • 通过同步函数实行读操作

  • 通过异步栅栏函数实现写操作

将同步函数和异步栅栏函数加入同一队列,可以满足读写互斥,写操作之间的互斥。并且异步栅栏函数只会阻塞自身队列中的任务,不会影响其他任务的执行

声明:

  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @property (nonatomic,strong) NSMutableDictionary *dic;
  4. @property (nonatomic,strong) dispatch_queue_t queue;
  5. @end

初始化:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. _dic = [NSMutableDictionary dictionary];
  4. _queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
  5. }

读操作:

  1. - (NSString *)safrGetter:(NSString *)strKey{
  2. __block NSString* strValue;
  3. dispatch_sync(self.queue, ^{
  4. strValue = [self.dic objectForKey:strKey];
  5. NSLog(@"safrGetter:%@,%@,%@",strKey,strValue,[NSThread currentThread]);
  6. });
  7. return strValue;
  8. }

写操作:

  1. - (void)safeSetter:(NSString *)strKey strValue:(NSString *)strValue{
  2. dispatch_barrier_async(self.queue, ^{
  3. [self.dic setObject:strValue forKey:strKey];
  4. NSLog(@"safeSetter:%@,%@,%@",strKey,strValue,[NSThread currentThread]);
  5. });
  6. }

测试代码:

  1. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  2. [self test];
  3. }
  4. -(void)test{
  5. for (int intIndex=0; intIndex<50; intIndex++) {
  6. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  7. [self safeSetter:[NSString stringWithFormat:@"key_%i",intIndex] strValue:[NSString stringWithFormat:@"value_%i",intIndex]];
  8. [self safrGetter:[NSString stringWithFormat:@"key_%i",intIndex]];
  9. });
  10. }
  11. }

总结

@synchronize

  • 采用SyncData结构,包含nextDatathreadCountrecursive_mutex_t递归锁

  • @synchronized在底层维护一张哈希表,用来存储SyncData的单向链表。当遇到哈希冲突时,存储在链表中

  • 同一对象,单一线程多次加锁使用lockCount记录,通过tls或缓存查找并记录

  • 同一对象,多线程加锁使用threadCount记录,通过哈希表查找并记录

  • 所以@synchronized更强大,可递归使用,可多线程使用

NSLock

  • 底层对pthread_mutex进行封装

  • 线程安全,但不能递归使用

NSRecursiveLock

  • 底层对pthread_mutex进行封装,通过PTHREAD_MUTEX_RECURSIVE标记为递归锁

  • 可递归使用,但不能多线程使用

NSCondition

  • 底层对pthread_mutex进行封装

  • 条件锁,需要手动让线程进入等待状态阻塞线程、释放信号唤醒线程

NSConditionLock

  • 底层基于NSCondition又做了一层封装

  • 条件锁,只需要外部传入一个值,就会依据这个值进行自动判断,决定阻塞线程还是唤醒线程

atomic

  • atomic是属性的修饰,不是锁。它在底层自带一把互斥锁,只是能保证数据的完整性,单一线程安全,不能保证多线程安全。锁操作导致效率低,不推荐使用

  • atomic:当属性在调用settergetter方法时,会加上互斥锁os_unfair_lock,用于保证同一时刻只能有一个线程调用属性的读或写,避免了属性读写不同步的问题

nonatomic

  • 当属性在调用settergetter方法时,没有任何锁的操作,线程不安全。效率高,推荐使用

OSSpinLock

  • 自旋锁,由于安全性问题,iOS10之后被os_unfair_lock替代

  • 目前OSSpinLock还可以使用,但不推荐

  • 使用OSSpinLock,会处于忙等待状态

os_unfair_lock

  • 互斥锁

  • 使用os_unfair_lock,会处于休眠状态

读写锁:

  • 多读单写

  • 读和写互斥

  • 写操作之间互斥

  • 不能阻塞正常的任务执行

  • 两种实现方式:

    • 使用pthread实现

    • 使用GCD实现