1、pthread_mutex简介

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态。
使用时需要导入头文件:

  1. #import <pthread/pthread.h>

有如下API:

  1. - (void)mutexAPI {
  2. // 初始化属性
  3. pthread_mutexattr_t attr;
  4. pthread_mutexattr_init(&attr);
  5. // 设置默认类型
  6. pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
  7. // 初始化锁
  8. pthread_mutex_t mutex;
  9. pthread_mutex_init(&mutex, &attr);
  10. // 尝试加锁
  11. pthread_mutex_trylock(&mutex);
  12. // 加锁
  13. pthread_mutex_lock(&mutex);
  14. // 解锁
  15. pthread_mutex_unlock(&mutex);
  16. // 销毁属性
  17. pthread_mutexattr_destroy(&attr);
  18. // 销毁锁
  19. pthread_mutex_destroy(&mutex);
  20. }

创建方法:

  1. - (void)__initMutex:(pthread_mutex_t *)mutex {
  2. // 初始化属性
  3. pthread_mutexattr_t attr;
  4. pthread_mutexattr_init(&attr);
  5. // 设置默认类型
  6. pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
  7. // 初始化锁
  8. pthread_mutex_init(mutex, &attr);
  9. //销毁属性
  10. pthread_mutexattr_destroy(&attr);
  11. }

加锁、解锁调用示例:

  1. - (void)__saleTicket {
  2. pthread_mutex_lock(&_ticketLock);
  3. [super __saleTicket];
  4. pthread_mutex_unlock(&_ticketLock);
  5. }

pthread_mutex在不使用的时候需要销毁:

  1. - (void)dealloc {
  2. // 销毁锁
  3. pthread_mutex_destroy(&_mutex);
  4. }

2、死锁问题

2.1、在加锁任务中,调用其他加锁的任务,且是同一把锁,会造成死锁

  1. - (void)otherTest {
  2. pthread_mutex_lock(&_mutex);
  3. NSLog(@"1");
  4. [self otherTest2];
  5. pthread_mutex_unlock(&_mutex);
  6. }
  7. - (void)otherTest2 {
  8. pthread_mutex_lock(&_mutex);
  9. NSLog(@"2");
  10. pthread_mutex_unlock(&_mutex);
  11. }

原因是第二次加锁会开始等待,导致无法解锁。

2.2、加锁任务递归调用,也会产生死锁

  1. - (void)otherTest {
  2. pthread_mutex_lock(&_mutex);
  3. NSLog(@"1");
  4. [self otherTest];
  5. pthread_mutex_unlock(&_mutex);
  6. }

原因和第个问相同。

3、pthread_mutex递归锁

递归锁允许同一个线程对同一把锁重复加锁,使用递归锁,就可以解决递归调用的死锁问题。
在创建mutex锁时,将mutex_attr的类型设置为递归:

  1. - (void)__initMutex:(pthread_mutex_t *)mutex {
  2. // 初始化属性
  3. pthread_mutexattr_t attr;
  4. pthread_mutexattr_init(&attr);
  5. // 设置递归类型
  6. pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
  7. // 初始化锁
  8. pthread_mutex_init(mutex, &attr);
  9. //销毁属性
  10. pthread_mutexattr_destroy(&attr);
  11. }

递归调用时就不会产生死锁:

  1. - (void)otherTest {
  2. pthread_mutex_lock(&_mutex);
  3. NSLog(@"1");
  4. [self otherTest];
  5. pthread_mutex_unlock(&_mutex);
  6. }

加锁解锁流程如下:
线程1访问:
边界条件不满足:递归前进段:——————
otherTest 加锁
otherTest 加锁
otherTest 加锁
边界条件满足:递归返回段:——————
otherTest 解锁
otherTest 解锁
otherTest 解锁

线程2访问:
进入等待阶段,等线程1全部解锁后,再执行任务。

4、pthread_mutex条件

当线程间存在依赖关系时,可以使用pthread_cond来实现。
pthread_cond有如下API:

  1. - (void)condAPI {
  2. // 初始化锁
  3. pthread_mutex_t mutex;
  4. // NULL代表使用默认属性
  5. pthread_mutex_init(&mutex, NULL);
  6. // 初始化条件
  7. pthread_cond_t conditon;
  8. pthread_cond_init(&conditon, NULL);
  9. // 等待条件(进入休眠,放开mutex锁;被唤醒后,会再次对mutex加锁)
  10. pthread_cond_wait(&conditon, &mutex);
  11. // 激活一个等待条件的线程
  12. pthread_cond_signal(&conditon);
  13. // 激活所有等待条件的线程
  14. pthread_cond_broadcast(&conditon);
  15. // 销毁锁
  16. pthread_mutex_destroy(&mutex);
  17. // 销毁条件
  18. pthread_cond_destroy(&conditon);
  19. }

例如创建两个线程和一个数组:
线程1:删除数组里的元素
线程2:向数组里添加元素
依赖关系:删除数组中的数据依赖于数组内有数据。
创建条件:

  1. // 初始化属性
  2. pthread_mutexattr_t attr;
  3. pthread_mutexattr_init(&attr);
  4. // 设置递归类型
  5. pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
  6. // 初始化锁
  7. pthread_mutex_init(&_mutex, &attr);
  8. //销毁属性
  9. pthread_mutexattr_destroy(&attr);
  10. // 创建条件
  11. pthread_cond_init(&_cond, NULL);
  12. self.arr = [[NSMutableArray alloc] init];

创建线程:

  1. - (void)otherTest {
  2. [[[NSThread alloc] initWithTarget:self selector:@selector(__remvoe) object:nil] start];
  3. sleep(0.1);
  4. [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
  5. }

删除元素时,如果数组是空,执行pthread_cond_wait方法,开始等待条件:

  1. - (void)__remove {
  2. pthread_mutex_lock(&_mutex);
  3. NSLog(@"线程1: 【加锁】");
  4. if (self.arr.count == 0) {
  5. NSLog(@"线程1: 开始等待(条件不成立,开始休眠,并【解锁】)");
  6. pthread_cond_wait(&_cond, &_mutex);
  7. NSLog(@"线程1: 结束等待(接收到条件成立,【加锁】并执行后续操作)");
  8. }
  9. NSLog(@"线程1: 删除元素");
  10. [self.arr removeLastObject];
  11. NSLog(@"线程1:【解锁】");
  12. pthread_mutex_unlock(&_mutex);
  13. }

添加元素后,调用pthread_cond_signal方法,通知条件成立:

  1. - (void)__add {
  2. pthread_mutex_lock(&_mutex);
  3. NSLog(@"线程2: 【加锁】");
  4. NSLog(@"线程2: 添加元素");
  5. [self.arr addObject:@"1"];
  6. NSLog(@"线程2: 通知线程1条件成立");
  7. pthread_cond_signal(&_cond);
  8. NSLog(@"线程2: 【解锁】");
  9. pthread_mutex_unlock(&_mutex);
  10. }

也可以通过pthread_cond_broadcast(&_cond);方法,利用广播方式通知所有依赖这个条件的线程,条件成立。

打印结果:

  1. ~: 线程1: 【加锁】
  2. ~: 线程1: 开始等待(条件不成立,开始休眠,并【解锁】)
  3. ~: 线程2: 【加锁】
  4. ~: 线程2: 添加元素
  5. ~: 线程2: 通知线程1条件成立
  6. ~: 线程2: 【解锁】
  7. ~: 线程1: 结束等待(接收到条件成立,【加锁】并执行后续操作)
  8. ~: 线程1: 删除元素
  9. ~: 线程1:【解锁】

*可以看出加锁和解锁的次数是一样的,且删除元素一定在添加元素后调用。