经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS底层原理(七)多线程(中)
来源:cnblogs  作者:FunkyRay  时间:2021/4/12 9:49:48  对本文有异议

多线程的安全隐患

一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源;当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题### 问题案例
卖票和存钱取钱的两个案例,具体见下面代码

  1. @interface BaseDemo: NSObject
  2. - (void)moneyTest;
  3. - (void)ticketTest;
  4. #pragma mark - 暴露给子类去使用
  5. - (void)__saveMoney;
  6. - (void)__drawMoney;
  7. - (void)__saleTicket;
  8. @end
  9. @interface BaseDemo()
  10. @property (assign, nonatomic) int money;
  11. @property (assign, nonatomic) int ticketsCount;
  12. @end
  13. @implementation BaseDemo
  14. /**
  15. 存钱、取钱演示
  16. */
  17. - (void)moneyTest
  18. {
  19. self.money = 100;
  20. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  21. dispatch_async(queue, ^{
  22. for (int i = 0; i < 10; i++) {
  23. [self __saveMoney];
  24. }
  25. });
  26. dispatch_async(queue, ^{
  27. for (int i = 0; i < 10; i++) {
  28. [self __drawMoney];
  29. }
  30. });
  31. }
  32. /**
  33. 存钱
  34. */
  35. - (void)__saveMoney
  36. {
  37. int oldMoney = self.money;
  38. sleep(.2);
  39. oldMoney += 50;
  40. self.money = oldMoney;
  41. NSLog(@"存50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
  42. }
  43. /**
  44. 取钱
  45. */
  46. - (void)__drawMoney
  47. {
  48. int oldMoney = self.money;
  49. sleep(.2);
  50. oldMoney -= 20;
  51. self.money = oldMoney;
  52. NSLog(@"取20,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
  53. }
  54. /**
  55. 卖1张票
  56. */
  57. - (void)__saleTicket
  58. {
  59. int oldTicketsCount = self.ticketsCount;
  60. sleep(.2);
  61. oldTicketsCount--;
  62. self.ticketsCount = oldTicketsCount;
  63. NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
  64. }
  65. /**
  66. 卖票演示
  67. */
  68. - (void)ticketTest
  69. {
  70. self.ticketsCount = 15;
  71. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  72. dispatch_async(queue, ^{
  73. for (int i = 0; i < 5; i++) {
  74. [self __saleTicket];
  75. }
  76. });
  77. dispatch_async(queue, ^{
  78. for (int i = 0; i < 5; i++) {
  79. [self __saleTicket];
  80. }
  81. });
  82. dispatch_async(queue, ^{
  83. for (int i = 0; i < 5; i++) {
  84. [self __saleTicket];
  85. }
  86. });
  87. }
  88. @end
  89. @interface ViewController ()
  90. @property (strong, nonatomic) BaseDemo *demo;
  91. @end
  92. @implementation ViewController
  93. - (void)viewDidLoad {
  94. [super viewDidLoad];
  95. BaseDemo *demo = [[BaseDemo alloc] init];
  96. [demo ticketTest];
  97. [demo moneyTest];
  98. }
  99. @end

解决方案是使用线程同步技术(同步,就是协同步调,按预定的先后次序进行,常见的方案就是加锁

线程同步方案

iOS中的线程同步方案有以下这些

  • OSSpinLock- os_unfair_lock- pthread_mutex- dispatch_semaphore- dispatch_queue(DISPATCH_QUEUE_SERIAL)- NSLock- NSRecursiveLock- NSCondition- NSConditionLock- @synchronized#### OSSpinLock
    OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源OSSpinLock来解决上述示例问题
  1. @interface OSSpinLockDemo: BaseDemo
  2. @end
  3. #import "OSSpinLockDemo.h"
  4. #import <libkern/OSAtomic.h>
  5. @interface OSSpinLockDemo()
  6. @property (assign, nonatomic) OSSpinLock moneyLock;
  7. // @property (assign, nonatomic) OSSpinLock ticketLock;
  8. @end
  9. @implementation OSSpinLockDemo
  10. - (instancetype)init
  11. {
  12. if (self = [super init]) {
  13. self.moneyLock = OS_SPINLOCK_INIT;
  14. // self.ticketLock = OS_SPINLOCK_INIT;
  15. }
  16. return self;
  17. }
  18. - (void)__drawMoney
  19. {
  20. OSSpinLockLock(&_moneyLock);
  21. [super __drawMoney];
  22. OSSpinLockUnlock(&_moneyLock);
  23. }
  24. - (void)__saveMoney
  25. {
  26. OSSpinLockLock(&_moneyLock);
  27. [super __saveMoney];
  28. OSSpinLockUnlock(&_moneyLock);
  29. }
  30. - (void)__saleTicket
  31. {
  32. // 不用属性,用一个静态变量也可以
  33. static OSSpinLock ticketLock = OS_SPINLOCK_INIT;
  34. OSSpinLockLock(&ticketLock);
  35. [super __saleTicket];
  36. OSSpinLockUnlock(&ticketLock);
  37. }
  38. @end
static的问题

上面的ticketLock也可以用static来修饰作为内部静态变量来使用

  1. #define OS_SPINLOCK_INIT 0

由于OS_SPINLOCK_INIT就是0,所以才可以用static来修饰;static只能在编译时赋值一个确定值,不能动态赋予一个函数值

  1. // 这样赋值一个函数返回值是会报错的
  2. static OSSpinLock ticketLock = [NSString stringWithFormat:@"haha"];
OSSpinLock的问题

OSSpinLock现在已经不再安全,可能会出现优先级反转问题

由于多线程的本质是在不同线程之间进行来回的调度,每个线程可能对应分配的资源优先级不同;如果优先级低的线程先进行了加锁并准备执行代码,这时优先级高的线程就会在外面循环等待加锁;但因为其优先级高,所以CPU可能会大量的给其分配任务,那么就没办法处理优先级低的线程;优先级低的线程就无法继续往下执行代码,那么也就没办法解锁,所以又会变成了互相等待的局面,造成死锁。这也是苹果现在废弃了OSSpinLock的原因

解决办法

用尝试加锁OSSpinLockTry来替换OSSpinLockLock,如果没有加锁才会进到判断里执行代码并加锁,避免了因上锁了一直在循环等待的问题

  1. // 用卖票的函数来举例,其他几个加锁的方法也是同样
  2. - (void)__saleTicket
  3. {
  4. if (OSSpinLockTry(&_ticketLock)) {
  5. [super __saleTicket];
  6. OSSpinLockUnlock(&_ticketLock);
  7. }
  8. }
通过汇编来分析

我们通过断点来分析加锁之后做了什么

我们在卖票的加锁代码处打上断点,并通过转汇编的方式一步步调用分析

转成汇编后调用OSSpinLockLock

内部会调用_OSSpinLockLockSlow

核心部分,在_OSSpinLockLockSlow会进行比较,然后执行到断点处又会再次跳回0x7fff5e73326f再次执行代码


所以通过汇编底层执行逻辑,我们能看出OSSpinLock是会不断循环去调用判断的,只有解锁之后才会往下执行代码

锁的等级

OSSpinLock自旋锁是高等级的锁(High-level lock),因为会一直循环调用

os_unfair_lock

苹果现在用os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持

从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等

修改示例代码如下

  1. #import "BaseDemo.h"
  2. @interface OSUnfairLockDemo: BaseDemo
  3. @end
  4. #import "OSUnfairLockDemo.h"
  5. #import <os/lock.h>
  6. @interface OSUnfairLockDemo()
  7. @property (assign, nonatomic) os_unfair_lock moneyLock;
  8. @property (assign, nonatomic) os_unfair_lock ticketLock;
  9. @end
  10. @implementation OSUnfairLockDemo
  11. - (instancetype)init
  12. {
  13. if (self = [super init]) {
  14. self.moneyLock = OS_UNFAIR_LOCK_INIT;
  15. self.ticketLock = OS_UNFAIR_LOCK_INIT;
  16. }
  17. return self;
  18. }
  19. - (void)__saleTicket
  20. {
  21. os_unfair_lock_lock(&_ticketLock);
  22. [super __saleTicket];
  23. os_unfair_lock_unlock(&_ticketLock);
  24. }
  25. - (void)__saveMoney
  26. {
  27. os_unfair_lock_lock(&_moneyLock);
  28. [super __saveMoney];
  29. os_unfair_lock_unlock(&_moneyLock);
  30. }
  31. - (void)__drawMoney
  32. {
  33. os_unfair_lock_lock(&_moneyLock);
  34. [super __drawMoney];
  35. os_unfair_lock_unlock(&_moneyLock);
  36. }
  37. @end

如果不写os_unfair_lock_unlock,那么所有的线程都会卡在os_unfair_lock_lock进入睡眠,不会再继续执行代码,这种情况叫做死锁

通过汇编来分析

我们也通过断点来分析加锁之后做了什么

首先会调用os_unfair_lock_lock

然后会调用os_unfair_lock_lock_slow

然后在os_unfair_lock_lock_slow中会执行__ulock_wait


核心部分,代码执行到syscall会直接跳出断点,不再执行代码,也就是进入了睡眠


所以通过汇编底层执行逻辑,我们能看出os_unfair_lock一旦进行了加锁,就会直接进入休眠,等待解锁后唤醒再继续执行代码,也由此可以认为os_unfair_lock是互斥锁

syscall的调用可以理解为系统级别的调用进入睡眠,会直接卡住线程,不再执行代码

锁的等级

我们进到os_unfair_lock的头文件lock.h,可以看到注释说明os_unfair_lock是一个低等级的锁(Low-level lock),因为一旦发现加锁后就会自动进入睡眠

pthread_mutex

互斥锁

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态

使用代码如下

  1. @interface MutexDemo: BaseDemo
  2. @end
  3. #import "MutexDemo.h"
  4. #import <pthread.h>
  5. @interface MutexDemo()
  6. @property (assign, nonatomic) pthread_mutex_t ticketMutex;
  7. @property (assign, nonatomic) pthread_mutex_t moneyMutex;
  8. @end
  9. @implementation MutexDemo
  10. - (void)__initMutex:(pthread_mutex_t *)mutex
  11. {
  12. // 静态初始化
  13. // 需要在定义这个变量时给予值才可以这么写
  14. // pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  15. // // 初始化属性
  16. // pthread_mutexattr_t attr;
  17. // pthread_mutexattr_init(&attr);
  18. // pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
  19. // // 初始化锁
  20. // pthread_mutex_init(mutex, &attr);
  21. // &attr传NULL默认就是PTHREAD_MUTEX_DEFAULT
  22. pthread_mutex_init(mutex, NULL);
  23. }
  24. - (instancetype)init
  25. {
  26. if (self = [super init]) {
  27. [self __initMutex:&_ticketMutex];
  28. [self __initMutex:&_moneyMutex];
  29. }
  30. return self;
  31. }
  32. - (void)__saleTicket
  33. {
  34. pthread_mutex_lock(&_ticketMutex);
  35. [super __saleTicket];
  36. pthread_mutex_unlock(&_ticketMutex);
  37. }
  38. - (void)__saveMoney
  39. {
  40. pthread_mutex_lock(&_moneyMutex);
  41. [super __saveMoney];
  42. pthread_mutex_unlock(&_moneyMutex);
  43. }
  44. - (void)__drawMoney
  45. {
  46. pthread_mutex_lock(&_moneyMutex);
  47. [super __drawMoney];
  48. pthread_mutex_unlock(&_moneyMutex);
  49. }
  50. - (void)dealloc
  51. {
  52. // 对象销毁时调用
  53. pthread_mutex_destroy(&_moneyMutex);
  54. pthread_mutex_destroy(&_ticketMutex);
  55. }

pthread_mutex_t实际就是pthread_mutex *类型

递归锁

当属性设置为PTHREAD_MUTEX_RECURSIVE时,就可以作为递归锁来使用

递归锁允许同一个线程对一把锁进行重复加锁;多个线程不可以用递归锁

  1. - (void)__initMutex:(pthread_mutex_t *)mutex
  2. {
  3. // 初始化属性
  4. pthread_mutexattr_t attr;
  5. pthread_mutexattr_init(&attr);
  6. pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
  7. // 初始化锁
  8. pthread_mutex_init(mutex, &attr);
  9. }
  10. - (void)otherTest
  11. {
  12. pthread_mutex_lock(&_mutex);
  13. NSLog(@"%s", __func__);
  14. static int count = 0;
  15. if (count < 10) {
  16. count++;
  17. [self otherTest];
  18. }
  19. pthread_mutex_unlock(&_mutex);
  20. }
根据条件来进行加锁

我们可以设定一定的条件来选择线程之间的调用进行加锁解锁,示例如下

  1. @interface MutexDemo()
  2. @property (assign, nonatomic) pthread_mutex_t mutex;
  3. @property (assign, nonatomic) pthread_cond_t cond;
  4. @property (strong, nonatomic) NSMutableArray *data;
  5. @end
  6. @implementation MutexDemo
  7. - (instancetype)init
  8. {
  9. if (self = [super init]) {
  10. // 初始化锁
  11. pthread_mutex_init(&_mutex, NULL);
  12. // 初始化条件
  13. pthread_cond_init(&_cond, NULL);
  14. self.data = [NSMutableArray array];
  15. }
  16. return self;
  17. }
  18. - (void)otherTest
  19. {
  20. [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
  21. [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
  22. }
  23. // 线程1
  24. // 删除数组中的元素
  25. - (void)__remove
  26. {
  27. pthread_mutex_lock(&_mutex);
  28. NSLog(@"__remove - begin");
  29. // 如果数据为空,那么设置条件等待唤醒
  30. // 等待期间会先解锁,让其他线程执行代码
  31. if (self.data.count == 0) {
  32. pthread_cond_wait(&_cond, &_mutex);
  33. }
  34. [self.data removeLastObject];
  35. NSLog(@"删除了元素");
  36. pthread_mutex_unlock(&_mutex);
  37. }
  38. // 线程2
  39. // 往数组中添加元素
  40. - (void)__add
  41. {
  42. pthread_mutex_lock(&_mutex);
  43. sleep(1);
  44. [self.data addObject:@"Test"];
  45. NSLog(@"添加了元素");
  46. // 一旦添加了元素,变发送条件信号,让等待删除的条件继续执行代码,并再次加锁
  47. // 信号(通知一个条件)
  48. pthread_cond_signal(&_cond);
  49. // 广播(通知所有条件)
  50. // pthread_cond_broadcast(&_cond);
  51. pthread_mutex_unlock(&_mutex);
  52. }
  53. - (void)dealloc
  54. {
  55. pthread_mutex_destroy(&_mutex);
  56. pthread_cond_destroy(&_cond);
  57. }
  58. @end
通过汇编来分析

我们通过断点来分析加锁之后做了什么

首先会执行pthread_mutex_lock

然后会执行pthread_mutex_firstfit_lock_slow


然后会执行pthread_mutex_firstfit_lock_wait


然后会执行__psynch_mutexwait

核心部分,在__psynch_mutexwait里,代码执行到syscall会直接跳出断点,不再执行代码,也就是进入了睡眠


所以pthread_mutexos_unfair_lock一样,都是在加锁之后会进入到睡眠

锁的等级

pthread_mutexos_unfair_lock一样,都是低等级的锁(Low-level lock)

NSLock

NSLock是对mutex普通锁的封装

NSLock遵守了<NSLocking>协议,支持以下两个方法

  1. @protocol NSLocking
  2. - (void)lock;
  3. - (void)unlock;
  4. @end

其他常用方法

  1. // 尝试解锁
  2. - (BOOL)tryLock;
  3. // 设定一个时间等待加锁,时间到了如果还不能成功加锁就返回NO
  4. - (BOOL)lockBeforeDate:(NSDate *)limit;

具体使用看下面代码

  1. @interface NSLockDemo: BaseDemo
  2. @end
  3. @interface NSLockDemo()
  4. @property (strong, nonatomic) NSLock *ticketLock;
  5. @property (strong, nonatomic) NSLock *moneyLock;
  6. @end
  7. @implementation NSLockDemo
  8. - (instancetype)init
  9. {
  10. if (self = [super init]) {
  11. self.ticketLock = [[NSLock alloc] init];
  12. self.moneyLock = [[NSLock alloc] init];
  13. }
  14. return self;
  15. }
  16. - (void)__saleTicket
  17. {
  18. [self.ticketLock lock];
  19. [super __saleTicket];
  20. [self.ticketLock unlock];
  21. }
  22. - (void)__saveMoney
  23. {
  24. [self.moneyLock lock];
  25. [super __saveMoney];
  26. [self.moneyLock unlock];
  27. }
  28. - (void)__drawMoney
  29. {
  30. [self.moneyLock lock];
  31. [super __drawMoney];
  32. [self.moneyLock unlock];
  33. }
  34. @end
分析底层实现

由于NSLock是不开源的,我们可以通过GNUstep Base来分析具体实现

找到NSLock.m可以看到initialize初始化方法里是创建的pthread_mutex_t对象,所以可以确定NSLock是对pthread_mutex的面向对象的封装

  1. @implementation NSLock
  2. + (id) allocWithZone: (NSZone*)z
  3. {
  4. if (self == baseLockClass && YES == traceLocks)
  5. {
  6. return class_createInstance(tracedLockClass, 0);
  7. }
  8. return class_createInstance(self, 0);
  9. }
  10. + (void) initialize
  11. {
  12. static BOOL beenHere = NO;
  13. if (beenHere == NO)
  14. {
  15. beenHere = YES;
  16. /* Initialise attributes for the different types of mutex.
  17. * We do it once, since attributes can be shared between multiple
  18. * mutexes.
  19. * If we had a pthread_mutexattr_t instance for each mutex, we would
  20. * either have to store it as an ivar of our NSLock (or similar), or
  21. * we would potentially leak instances as we couldn't destroy them
  22. * when destroying the NSLock. I don't know if any implementation
  23. * of pthreads actually allocates memory when you call the
  24. * pthread_mutexattr_init function, but they are allowed to do so
  25. * (and deallocate the memory in pthread_mutexattr_destroy).
  26. */
  27. pthread_mutexattr_init(&attr_normal);
  28. pthread_mutexattr_settype(&attr_normal, PTHREAD_MUTEX_NORMAL);
  29. pthread_mutexattr_init(&attr_reporting);
  30. pthread_mutexattr_settype(&attr_reporting, PTHREAD_MUTEX_ERRORCHECK);
  31. pthread_mutexattr_init(&attr_recursive);
  32. pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE);
  33. /* To emulate OSX behavior, we need to be able both to detect deadlocks
  34. * (so we can log them), and also hang the thread when one occurs.
  35. * the simple way to do that is to set up a locked mutex we can
  36. * force a deadlock on.
  37. */
  38. pthread_mutex_init(&deadlock, &attr_normal);
  39. pthread_mutex_lock(&deadlock);
  40. baseConditionClass = [NSCondition class];
  41. baseConditionLockClass = [NSConditionLock class];
  42. baseLockClass = [NSLock class];
  43. baseRecursiveLockClass = [NSRecursiveLock class];
  44. tracedConditionClass = [GSTracedCondition class];
  45. tracedConditionLockClass = [GSTracedConditionLock class];
  46. tracedLockClass = [GSTracedLock class];
  47. tracedRecursiveLockClass = [GSTracedRecursiveLock class];
  48. untracedConditionClass = [GSUntracedCondition class];
  49. untracedConditionLockClass = [GSUntracedConditionLock class];
  50. untracedLockClass = [GSUntracedLock class];
  51. untracedRecursiveLockClass = [GSUntracedRecursiveLock class];
  52. }
  53. }

NSRecursiveLock

NSRecursiveLock也是对mutex递归锁的封装,APINSLock基本一致

NSCondition

NSCondition是对mutexcond的封装

具体使用代码如下

  1. @interface NSConditionDemo()
  2. @property (strong, nonatomic) NSCondition *condition;
  3. @property (strong, nonatomic) NSMutableArray *data;
  4. @end
  5. @implementation NSConditionDemo
  6. - (instancetype)init
  7. {
  8. if (self = [super init]) {
  9. self.condition = [[NSCondition alloc] init];
  10. self.data = [NSMutableArray array];
  11. }
  12. return self;
  13. }
  14. - (void)otherTest
  15. {
  16. [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
  17. [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
  18. }
  19. // 线程1
  20. // 删除数组中的元素
  21. - (void)__remove
  22. {
  23. [self.condition lock];
  24. NSLog(@"__remove - begin");
  25. if (self.data.count == 0) {
  26. // 等待
  27. [self.condition wait];
  28. }
  29. [self.data removeLastObject];
  30. NSLog(@"删除了元素");
  31. [self.condition unlock];
  32. }
  33. // 线程2
  34. // 往数组中添加元素
  35. - (void)__add
  36. {
  37. [self.condition lock];
  38. sleep(1);
  39. [self.data addObject:@"Test"];
  40. NSLog(@"添加了元素");
  41. // 信号
  42. [self.condition signal];
  43. // 广播
  44. // [self.condition broadcast];
  45. [self.condition unlock];
  46. }
  47. @end
  48. ```##### 分析底层实现`NSCondition`也遵守了`NSLocking`协议,说明其内部已经封装了锁的相关代码

@interface NSCondition : NSObject {
@private
void *_priv;
}

  • (void)wait;
  • (BOOL)waitUntilDate:(NSDate *)limit;
  • (void)signal;
  • (void)broadcast;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

  1. 我们通过`GNUstep Base`也可以看到其初始化方法里对`pthread_mutex_t`进行了封装

@implementation NSCondition

  • (id) allocWithZone: (NSZone*)z
    {
    if (self == baseConditionClass && YES == traceLocks)
    {
    return class_createInstance(tracedConditionClass, 0);
    }
    return class_createInstance(self, 0);
    }

  • (void) initialize
    {
    [NSLock class]; // Ensure mutex attributes are set up.
    }

  • (id) init
    {
    if (nil != (self = [super init]))
    {
    if (0 != pthread_cond_init(&_condition, NULL))
    {
    DESTROY(self);
    }
    else if (0 != pthread_mutex_init(&_mutex, &attr_reporting))
    {
    pthread_cond_destroy(&_condition);
    DESTROY(self);
    }
    }
    return self;
    }
  1. #### NSConditionLock
  2. `NSConditionLock`是对`NSCondition`的进一步封装,可以设置具体的条件值
  3. 通过设置条件值可以对线程做依赖控制执行顺序,具体使用见示例代码

@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end

@implementation NSConditionLockDemo

  • (instancetype)init
    {
    // 创建的时候可以设置一个条件
    // 如果不设置,默认就是0
    if (self = [super init]) {
    self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    }
    return self;
    }

  • (void)otherTest
    {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];

    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];

    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
    }

  • (void)__one
    {
    // 不需要任何条件,只有没有锁就加锁
    [self.conditionLock lock];

    NSLog(@"__one");
    sleep(1);

    [self.conditionLock unlockWithCondition:2];
    }

  • (void)__two
    {
    // 根据对应条件来加锁
    [self.conditionLock lockWhenCondition:2];

    NSLog(@"__two");
    sleep(1);

    [self.conditionLock unlockWithCondition:3];
    }

  • (void)__three
    {
    [self.conditionLock lockWhenCondition:3];

    NSLog(@"__three");

    [self.conditionLock unlock];
    }

@end

// 打印的先后顺序为:1、2、3

  1. #### dispatch\_queue\_t
  2. 我们可以直接使用`GCD`的串行队列,也是可以实现线程同步的,具体代码可以参考`GCD`部分的示例代码
  3. #### dispatch_semaphore
  4. `semaphore`叫做”信号量”
  5. 信号量的初始值,可以用来控制线程并发访问的最大数量
  6. 示例代码如下

@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore;
@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;
@end

@implementation SemaphoreDemo

  • (instancetype)init
    {
    if (self = [super init]) {
    // 初始化信号量
    // 最多只开5条线程,也就是可以5条线程同时访问同一块空间,然后加锁,其他线程再进来就只能等待了
    self.semaphore = dispatch_semaphore_create(5);
    // 最多只开1条线程
    self.ticketSemaphore = dispatch_semaphore_create(1);
    self.moneySemaphore = dispatch_semaphore_create(1);
    }
    return self;
    }

  • (void)__drawMoney
    {
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);

    [super __drawMoney];

    dispatch_semaphore_signal(self.moneySemaphore);
    }

  • (void)__saveMoney
    {
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);

    [super __saveMoney];

    dispatch_semaphore_signal(self.moneySemaphore);
    }

  • (void)__saleTicket
    {
    // 如果信号量的值>0就减1,然后往下执行代码
    // 当信号量的值<=0时,当前线程就会进入休眠等待(直到信号量的值>0)
    dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);

    [super __saleTicket];

    // 让信号量的值加1
    dispatch_semaphore_signal(self.ticketSemaphore);
    }

@end

  1. #### @synchronized
  2. `@synchronized`是对`mutex`递归锁的封装
  3. 示例代码如下

@interface SynchronizedDemo: BaseDemo

@end

@implementation SynchronizedDemo

  • (void)__drawMoney
    {
    // @synchronized需要加锁的是同一个对象才行
    @synchronized([self class]) {
    [super __drawMoney];
    }
    }

  • (void)__saveMoney
    {
    @synchronized([self class]) { // objc_sync_enter
    [super __saveMoney];
    } // objc_sync_exit
    }

  • (void)__saleTicket
    {
    static NSObject *lock;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    lock = [[NSObject alloc] init];
    });

    @synchronized(lock) {
    [super __saleTicket];
    }
    }
    @end

  1. ##### 源码分析
  2. 我们可以通过程序运行中转汇编看到,最终都会调用`objc_sync_enter`
  3. ![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210408144446882-975064667.jpg)
  4. 我们可以通过`objc4``objc-sync.mm`来分析对应的源码实现

int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;

  1. if (obj) {
  2. SyncData* data = id2data(obj, ACQUIRE);
  3. ASSERT(data);
  4. data->mutex.lock();
  5. } else {
  6. // @synchronized(nil) does nothing
  7. if (DebugNilSync) {
  8. _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
  9. }
  10. objc_sync_nil();
  11. }
  12. return result;

}

  1. 可以看到会根据传进来的`obj`找到对应的`SyncData`

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;

  1. 在找到`SyncData`里面的成员变量`recursive_mutex_t`的真实类型,里面有一个递归锁

using recursive_mutex_t = recursive_mutex_tt;

class recursive_mutex_tt : nocopy_t {
os_unfair_recursive_lock mLock; // 递归锁

public:
constexpr recursive_mutex_tt() : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) {
lockdebug_remember_recursive_mutex(this);
}

  1. constexpr recursive_mutex_tt(__unused const fork_unsafe_lock_t unsafe)
  2. : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT)
  3. { }
  4. void lock()
  5. {
  6. lockdebug_recursive_mutex_lock(this);
  7. os_unfair_recursive_lock_lock(&mLock);
  8. }
  9. void unlock()
  10. {
  11. lockdebug_recursive_mutex_unlock(this);
  12. os_unfair_recursive_lock_unlock(&mLock);
  13. }
  14. void forceReset()
  15. {
  16. lockdebug_recursive_mutex_unlock(this);
  17. bzero(&mLock, sizeof(mLock));
  18. mLock = os_unfair_recursive_lock OS_UNFAIR_RECURSIVE_LOCK_INIT;
  19. }
  20. bool tryLock()
  21. {
  22. if (os_unfair_recursive_lock_trylock(&mLock)) {
  23. lockdebug_recursive_mutex_lock(this);
  24. return true;
  25. }
  26. return false;
  27. }
  28. bool tryUnlock()
  29. {
  30. if (os_unfair_recursive_lock_tryunlock4objc(&mLock)) {
  31. lockdebug_recursive_mutex_unlock(this);
  32. return true;
  33. }
  34. return false;
  35. }
  36. void assertLocked() {
  37. lockdebug_recursive_mutex_assert_locked(this);
  38. }
  39. void assertUnlocked() {
  40. lockdebug_recursive_mutex_assert_unlocked(this);
  41. }

};

  1. 然后我们分析获取`SyncData`的实现方法`id2data`,通过`obj``LIST_FOR_OBJ`真正取出数据

static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData *listp = &LIST_FOR_OBJ(object);
SyncData
result = NULL;

if SUPPORT_DIRECT_THREAD_KEYS

  1. // Check per-thread single-entry fast cache for matching object
  2. bool fastCacheOccupied = NO;
  3. SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
  4. if (data) {
  5. fastCacheOccupied = YES;
  6. if (data->object == object) {
  7. // Found a match in fast cache.
  8. uintptr_t lockCount;
  9. result = data;
  10. lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
  11. if (result->threadCount <= 0 || lockCount <= 0) {
  12. _objc_fatal("id2data fastcache is buggy");
  13. }
  14. switch(why) {
  15. case ACQUIRE: {
  16. lockCount++;
  17. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
  18. break;
  19. }
  20. case RELEASE:
  21. lockCount--;
  22. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
  23. if (lockCount == 0) {
  24. // remove from fast cache
  25. tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
  26. // atomic because may collide with concurrent ACQUIRE
  27. OSAtomicDecrement32Barrier(&result->threadCount);
  28. }
  29. break;
  30. case CHECK:
  31. // do nothing
  32. break;
  33. }
  34. return result;
  35. }
  36. }

endif

  1. // Check per-thread cache of already-owned locks for matching object
  2. SyncCache *cache = fetch_cache(NO);
  3. if (cache) {
  4. unsigned int i;
  5. for (i = 0; i < cache->used; i++) {
  6. SyncCacheItem *item = &cache->list[i];
  7. if (item->data->object != object) continue;
  8. // Found a match.
  9. result = item->data;
  10. if (result->threadCount <= 0 || item->lockCount <= 0) {
  11. _objc_fatal("id2data cache is buggy");
  12. }
  13. switch(why) {
  14. case ACQUIRE:
  15. item->lockCount++;
  16. break;
  17. case RELEASE:
  18. item->lockCount--;
  19. if (item->lockCount == 0) {
  20. // remove from per-thread cache
  21. cache->list[i] = cache->list[--cache->used];
  22. // atomic because may collide with concurrent ACQUIRE
  23. OSAtomicDecrement32Barrier(&result->threadCount);
  24. }
  25. break;
  26. case CHECK:
  27. // do nothing
  28. break;
  29. }
  30. return result;
  31. }
  32. }
  33. // Thread cache didn't find anything.
  34. // Walk in-use list looking for matching object
  35. // Spinlock prevents multiple threads from creating multiple
  36. // locks for the same new object.
  37. // We could keep the nodes in some hash table if we find that there are
  38. // more than 20 or so distinct locks active, but we don't do that now.
  39. lockp->lock();
  40. {
  41. SyncData* p;
  42. SyncData* firstUnused = NULL;
  43. for (p = *listp; p != NULL; p = p->nextData) {
  44. if ( p->object == object ) {
  45. result = p;
  46. // atomic because may collide with concurrent RELEASE
  47. OSAtomicIncrement32Barrier(&result->threadCount);
  48. goto done;
  49. }
  50. if ( (firstUnused == NULL) && (p->threadCount == 0) )
  51. firstUnused = p;
  52. }
  53. // no SyncData currently associated with object
  54. if ( (why == RELEASE) || (why == CHECK) )
  55. goto done;
  56. // an unused one was found, use it
  57. if ( firstUnused != NULL ) {
  58. result = firstUnused;
  59. result->object = (objc_object *)object;
  60. result->threadCount = 1;
  61. goto done;
  62. }
  63. }
  64. // Allocate a new SyncData and add to list.
  65. // XXX allocating memory with a global lock held is bad practice,
  66. // might be worth releasing the lock, allocating, and searching again.
  67. // But since we never free these guys we won't be stuck in allocation very often.
  68. posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
  69. result->object = (objc_object *)object;
  70. result->threadCount = 1;
  71. new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
  72. result->nextData = *listp;
  73. *listp = result;

done:
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {
// Probably some thread is incorrectly exiting
// while the object is held by another thread.
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");

if SUPPORT_DIRECT_THREAD_KEYS

  1. if (!fastCacheOccupied) {
  2. // Save in fast thread cache
  3. tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
  4. tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
  5. } else

endif

  1. {
  2. // Save in thread cache
  3. if (!cache) cache = fetch_cache(YES);
  4. cache->list[cache->used].data = result;
  5. cache->list[cache->used].lockCount = 1;
  6. cache->used++;
  7. }
  8. }
  9. return result;

}

  1. `LIST_FOR_OBJ`是一个哈希表,哈希表的实现就是将传进来的`obj`作为`key`,然后对应的锁为`value`

define LIST_FOR_OBJ(obj) sDataLists[obj].data // 哈希表

static StripedMap sDataLists;
// 哈希表的实现就是将传进来的对象作为key,然后对应的锁为value

  1. 通过源码分析我们也可以看出,`@synchronized`内部的锁是递归锁
  2. ### 锁的比较
  3. #### 性能比较排序
  4. 下面是每个锁按性能从高到低排序
  5. - os\_unfair\_lock- OSSpinLock- dispatch\_semaphore- pthread\_mutex- dispatch\_queue(DISPATCH\_QUEUE\_SERIAL)- NSLock- NSCondition- pthread_mutex(recursive)- NSRecursiveLock- NSConditionLock- @synchronized
  6. 选择性最高的锁
  7. - dispatch\_semaphore- pthread\_mutex
  8. #### 互斥锁、自旋锁的比较
  9. ##### 什么情况使用自旋锁
  10. - 预计线程等待锁的时间很短- 加锁的代码(临界区)经常被调用,但竞争情况很少发生- CPU资源不紧张- 多核处理器
  11. ##### 什么情况使用互斥锁
  12. - 预计线程等待锁的时间较长- 单核处理器(尽量减少CPU的消耗)- 临界区有IO操作(IO操作比较占用CPU资源)- 临界区代码复杂或者循环量大- 临界区竞争非常激烈

原文链接:http://www.cnblogs.com/funkyRay/p/ios-di-ceng-yuan-li-qi-duo-xian-cheng-zhong.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号