iOS_多线程五:基础的9种锁,扩展12种使用

2022-07-20 14:10:31 浏览数 (1)

补充一下,总结的两张图,5类锁,9种Lock:

首先理解几个锁的概念:

互斥锁(mutexlock)sleep-waiting:

保证共享数据操作的完整性, 锁被占用的时候会休眠, 等待锁释放的时候会唤醒。

在访问共享资源之前进行加锁,访问完成后解锁。

加锁后,任何其他试图加锁的线程会被阻塞,直到当前线程解锁。

解锁时,如果有1个以上的线程阻塞,那么所有该锁上的线程变为就绪状态,第一个就绪的加锁,其他的又进入休眠。

从而实现在任意时刻,最多只有1个线程能够访问被互斥锁保护的资源。

自旋锁(spinlock)busy-waiting:

跟互斥类似, 只是资源被占用的时候, 会一直循环检测锁是否被释放(CPU不能做其他的事情)

节省了唤醒睡眠线程的内核消耗(在加锁时间短暂的情况下会大大提高效率)

读写锁(rwlock):

顾名思义 - 写的时候:读写都等待;读的时候:写等待,读无需等待

适合读次数远远大于写的情况 (为了公平,应该设计为:写操作到来时,后面的读阻塞)

条件锁(condlock):

线程会因为条件变量不满足而阻塞,线程也可以在释放锁时将条件变量改成某个值,从而唤醒满足条件变量的线程

递归锁(recursivelock):

跟互斥类似, 但是允许同一个线程在未释放锁前,加锁N次锁, 不会引发死锁

死锁:

这里给出的是同一个线程,在解锁之前,多次上锁的例子:会导致死锁。下面任意一个递归锁都可以解决这个导致死锁的问题。

代码语言:javascript复制
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  static void (^RecursiveMethod)(int);
  RecursiveMethod = ^(int value) {
    [lock lock];
    if (value > 0) {
      NSLog(@"加锁 %d", value);
      sleep(2);
      RecursiveMethod(value - 1); // 解锁之前又加想锁, 需要等待锁的解除,
    }
    NSLog(@"解锁 %d", value);
    [lock unlock];
  };
  RecursiveMethod(5);
});

接下来按目前的耗时排序介绍,先耗时短的:

1、OSSpinLock 自旋锁

会造成优先级反转的问题,iOS10 已废弃,有兴趣可以看看:不再去安全的OSSpinLock。这里也给出一下使用例子:

代码语言:javascript复制
NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
__block OSSpinLock osslock = OS_SPINLOCK_INIT; // 初始化
for (int i = 0; i < items.count; i  ) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    OSSpinLockLock(&osslock); // 加锁
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"%@", self.items);
    OSSpinLockUnlock(&osslock); // 解锁
  });
}

2、os_unfair_lock 互斥锁 iOS10

iOS10 才支持,为了代替OSSpinLock,需要: #import <os/lock.h>。使用例子:

代码语言:javascript复制
for (int i = 0; i < items.count; i  ) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT); // 必须在线程里初始化
    os_unfair_lock_lock(unfairLock); // 加锁
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"线程1 资源1: %@", self.items);
    os_unfair_lock_unlock(unfairLock); // 解锁
  });
}

3、dispatch_semaphore 信号量

保证关键代码不并发执行。使用例子:

代码语言:javascript复制
NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < items.count; i  ) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 加锁
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"%@", self.items);
    dispatch_semaphore_signal(semaphore); // 解锁
  });
}

4、pthread_mutex 互斥锁

苹果做出了优化, 性能不比semaphore差, 而且肯定安全(它有两种初始化方法,第二种带参数的可以设置type,下面会介绍)。需要:#import <pthread.h>。使用例子:

代码语言:javascript复制
pthread_mutex_t _pthread;
pthread_mutex_init(&_pthread, NULL); // _pthread 不能是局部变量!!
NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
for (int i = 0; i < items.count; i  ) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    pthread_mutex_lock(&_pthread); // 加锁
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"%@", self.items);
    pthread_mutex_unlock(&_pthread); // 解锁
  });
}

5、NSLock 互斥锁

很方便,竞争的是这个锁对象。使用例子:

代码语言:javascript复制
self.lock = [[NSLock alloc] init];
self.lock.name = @"itemsLock";

NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
for (int i = 0; i < items.count; i  ) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self.lock lock]; // 加锁
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"%@", self.items);
    [self.lock unlock]; // 解锁
  });
}

6、NSCondition 条件锁

内部有实现NSLocking协议, 就能实现NSLock所有功能。使用例子:

代码语言:javascript复制
NSCondition *lock = [[NSCondition alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  NSLog(@"start 1");
  [lock lock];
//    [lock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; // 让当前线程等待一段时间
  [lock wait];
  NSLog(@"end 1");
  [lock unlock];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  NSLog(@"start 2");
  [lock lock];
  [lock wait];
  NSLog(@"end 2");
  [lock unlock];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  NSLog(@"start 3");
  sleep(2);
//    [lock signal]; // 唤醒一个等待的线程
  [lock broadcast]; // 唤醒所有等待的线程
  NSLog(@"end 3");
});

7、pthread_mutex(recursive) 递归锁

需要设置 type = PTHREAD_MUTEX_RECURSIVE。

代码语言:javascript复制
pthread_mutex_t plock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); // 初始化attr, 并赋予默认
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 设置type为递归锁
pthread_mutex_init(&plock, &attr); // 若为 pthread_mutex_init(&plock, NULL) 则会死锁
pthread_mutexattr_destroy(&attr); // 销毁一个属性对象,在重新初始化之前该结构不能重复使用

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  static void (^RecursiveMethod)(int);
  RecursiveMethod = ^(int value) {
    NSLog(@"加锁: %d", value);
    pthread_mutex_lock(&plock); // 加锁
    if (value < 5) {
      sleep(2);
      RecursiveMethod(  value); // 解锁之前又加想锁, 需要等待锁的解除,
    }
    NSLog(@"解锁: %d", value);
    pthread_mutex_unlock(&plock); // 解锁
  };
  RecursiveMethod(1);
  NSLog(@"finish");
});

atrr的始终类型:

  •   PTHREAD_MUTEX_NORMAL: 互斥锁不会检测死锁。

   当一个线程解锁之前又锁上,将导致死锁。

   尝试解除其他线程上的锁,结果不可预测。

   尝试解除一个未锁定的锁,结果不可预测。

  •   PTHREAD_MUTEX_ERRORCHECK: 互斥锁提供错误检查。

   当一个线程尝试重新锁定一个还未解开的锁时,将会返回一个错误。

   尝试解除其他线程上的锁,将会返回一个错误。

   尝试解除一个未锁定的锁,将会返回一个错误。

  •   PTHREAD_MUTEX_RECURSIVE: 递归锁

   一个线程可以多次锁定一个还未解开的锁,需要相同数量的解锁来释放锁,然后另一个线程才能获的互斥锁

   尝试解除其他线程上的锁,将会返回一个错误。

   尝试解除一个未锁定的锁,将会返回一个错误。

  •   PTHREAD_MUTEX_DEFAULT:

   尝试递归锁定此类型的锁,结果不可预测。

   尝试解除其他线程上的锁,结果不可预测。

   尝试解除一个未锁定的锁,结果不可预测。

  8、NSRecursiveLock 递归锁

直接操作lock对象很方便,竞争的是这个锁对象。使用例子:

代码语言:javascript复制
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
//  [lock tryLock];
//  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:2]];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  static void (^RecursiveMethod)(int);
  RecursiveMethod = ^(int value) {
    NSLog(@"加锁: %d", value);
    [lock lock]; // 加锁
    if (value < 5) {
      sleep(2);
      RecursiveMethod(  value); // 解锁之前又锁上
    }
    NSLog(@"解锁: %d", value);
    [lock unlock]; // 解锁
  };
  RecursiveMethod(1);
  NSLog(@"finish");
});

9、NSConditionLock 条件锁

代码语言:javascript复制
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0]; // 设置初始标志位
//  [cLock lock]; // 不区分条件, 可加锁就执行
//  [cLock unlock]; // 不会清空条件, 之后满足条件的锁还会执行
//  [cLock tryLockWhenCondition:n]; // 标志位为n时, 加锁成功
//  [cLock unlockWithCondition:n]; // 解锁, 并设置标志位
//  [cLock lockWhenCondition:n]; // 标志位为n时加锁, 执行之后代码

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  if ([cLock tryLockWhenCondition:0]) {
    NSLog(@"任务1");
    [cLock unlockWithCondition:1];
  } else {
    NSLog(@"任务1 加锁失败");
  }
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  [cLock lockWhenCondition:3];
  NSLog(@"任务2");
  [cLock unlockWithCondition:2];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  [cLock lockWhenCondition:1];
  NSLog(@"任务3");
  [cLock unlockWithCondition:3];
});

10、@synchronized 递归锁

用pthread_mutex_t实现的。参数只是一个标识符,当多个异步线程传入的参数一致时会触发锁机制,当不一致时,则不会阻塞。

代码语言:javascript复制
NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
for (int i = 0; i < items.count; i  ) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized (self.items) { // 加锁
      sleep(items.count - i);
      [self.items addObject:items[i]];
      NSLog(@"%@", self.items);
    }
  });
}

11、POSIXConditions 条件锁:互斥锁 条件锁

参考 线程被一个 互斥 和 条件 结合的信号来唤醒,当锁和条件同时满足时,才能执行:

代码语言:javascript复制
- (void)POSIX_Codictions {
  ready_to_go = false;
  pthread_mutex_init(&mutex, NULL);
  pthread_cond_init(&condition, NULL);
  
  [self waitOnConditionFunction];
  [self signalThreadUsingCondition];
}
- (void)waitOnConditionFunction {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    pthread_mutex_lock(&mutex); // Lock the mutex.
    while(ready_to_go == false) {
      NSLog(@"wait...");
      pthread_cond_wait(&condition, &mutex); // 休眠
    }
    NSLog(@"done");
    ready_to_go = false;
    pthread_mutex_unlock(&mutex);
  });
}
- (void)signalThreadUsingCondition {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    pthread_mutex_lock(&mutex); // Lock the mutex.
    ready_to_go = true;
    NSLog(@"true");
    pthread_cond_signal(&condition); // Signal the other thread to begin work.
    pthread_mutex_unlock(&mutex);
  });
}

12、pthreadReadWrite 读写锁

代码语言:javascript复制
__block pthread_rwlock_t rwLock;
pthread_rwlock_init(&rwLock, NULL);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  pthread_rwlock_wrlock(&rwLock);
  NSLog(@"3 写 begin");
  sleep(3);
  NSLog(@"3 写 end");
  pthread_rwlock_unlock(&rwLock);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  pthread_rwlock_rdlock(&rwLock);
  NSLog(@"1 读 begin");
  sleep(1);
  NSLog(@"1 读 end");
  pthread_rwlock_unlock(&rwLock);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  pthread_rwlock_rdlock(&rwLock);
  NSLog(@"2 读 begin");
  sleep(2);
  NSLog(@"2 读 end");
  pthread_rwlock_unlock(&rwLock);
});

Demo github地址

0 人点赞