1、pthread_mutex简介
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态。
使用时需要导入头文件:
#import <pthread/pthread.h>
有如下API:
- (void)mutexAPI {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// 设置默认类型
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
// 尝试加锁
pthread_mutex_trylock(&mutex);
// 加锁
pthread_mutex_lock(&mutex);
// 解锁
pthread_mutex_unlock(&mutex);
// 销毁属性
pthread_mutexattr_destroy(&attr);
// 销毁锁
pthread_mutex_destroy(&mutex);
}
创建方法:
- (void)__initMutex:(pthread_mutex_t *)mutex {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// 设置默认类型
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_init(mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);
}
加锁、解锁调用示例:
- (void)__saleTicket {
pthread_mutex_lock(&_ticketLock);
[super __saleTicket];
pthread_mutex_unlock(&_ticketLock);
}
pthread_mutex在不使用的时候需要销毁:
- (void)dealloc {
// 销毁锁
pthread_mutex_destroy(&_mutex);
}
2、死锁问题
2.1、在加锁任务中,调用其他加锁的任务,且是同一把锁,会造成死锁
- (void)otherTest {
pthread_mutex_lock(&_mutex);
NSLog(@"1");
[self otherTest2];
pthread_mutex_unlock(&_mutex);
}
- (void)otherTest2 {
pthread_mutex_lock(&_mutex);
NSLog(@"2");
pthread_mutex_unlock(&_mutex);
}
2.2、加锁任务递归调用,也会产生死锁
- (void)otherTest {
pthread_mutex_lock(&_mutex);
NSLog(@"1");
[self otherTest];
pthread_mutex_unlock(&_mutex);
}
3、pthread_mutex递归锁
递归锁允许同一个线程对同一把锁重复加锁,使用递归锁,就可以解决递归调用的死锁问题。
在创建mutex锁时,将mutex_attr的类型设置为递归:
- (void)__initMutex:(pthread_mutex_t *)mutex {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// 设置递归类型
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);
}
递归调用时就不会产生死锁:
- (void)otherTest {
pthread_mutex_lock(&_mutex);
NSLog(@"1");
[self otherTest];
pthread_mutex_unlock(&_mutex);
}
加锁解锁流程如下:
线程1访问:
边界条件不满足:递归前进段:——————
otherTest 加锁
otherTest 加锁
otherTest 加锁
边界条件满足:递归返回段:——————
otherTest 解锁
otherTest 解锁
otherTest 解锁
4、pthread_mutex条件
当线程间存在依赖关系时,可以使用pthread_cond来实现。
pthread_cond有如下API:
- (void)condAPI {
// 初始化锁
pthread_mutex_t mutex;
// NULL代表使用默认属性
pthread_mutex_init(&mutex, NULL);
// 初始化条件
pthread_cond_t conditon;
pthread_cond_init(&conditon, NULL);
// 等待条件(进入休眠,放开mutex锁;被唤醒后,会再次对mutex加锁)
pthread_cond_wait(&conditon, &mutex);
// 激活一个等待条件的线程
pthread_cond_signal(&conditon);
// 激活所有等待条件的线程
pthread_cond_broadcast(&conditon);
// 销毁锁
pthread_mutex_destroy(&mutex);
// 销毁条件
pthread_cond_destroy(&conditon);
}
例如创建两个线程和一个数组:
线程1:删除数组里的元素
线程2:向数组里添加元素
依赖关系:删除数组中的数据依赖于数组内有数据。
创建条件:
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// 设置递归类型
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);
// 创建条件
pthread_cond_init(&_cond, NULL);
self.arr = [[NSMutableArray alloc] init];
创建线程:
- (void)otherTest {
[[[NSThread alloc] initWithTarget:self selector:@selector(__remvoe) object:nil] start];
sleep(0.1);
[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
删除元素时,如果数组是空,执行pthread_cond_wait方法,开始等待条件:
- (void)__remove {
pthread_mutex_lock(&_mutex);
NSLog(@"线程1: 【加锁】");
if (self.arr.count == 0) {
NSLog(@"线程1: 开始等待(条件不成立,开始休眠,并【解锁】)");
pthread_cond_wait(&_cond, &_mutex);
NSLog(@"线程1: 结束等待(接收到条件成立,【加锁】并执行后续操作)");
}
NSLog(@"线程1: 删除元素");
[self.arr removeLastObject];
NSLog(@"线程1:【解锁】");
pthread_mutex_unlock(&_mutex);
}
添加元素后,调用pthread_cond_signal方法,通知条件成立:
- (void)__add {
pthread_mutex_lock(&_mutex);
NSLog(@"线程2: 【加锁】");
NSLog(@"线程2: 添加元素");
[self.arr addObject:@"1"];
NSLog(@"线程2: 通知线程1条件成立");
pthread_cond_signal(&_cond);
NSLog(@"线程2: 【解锁】");
pthread_mutex_unlock(&_mutex);
}
也可以通过pthread_cond_broadcast(&_cond);方法,利用广播方式通知所有依赖这个条件的线程,条件成立。
打印结果:
~: 线程1: 【加锁】
~: 线程1: 开始等待(条件不成立,开始休眠,并【解锁】)
~: 线程2: 【加锁】
~: 线程2: 添加元素
~: 线程2: 通知线程1条件成立
~: 线程2: 【解锁】
~: 线程1: 结束等待(接收到条件成立,【加锁】并执行后续操作)
~: 线程1: 删除元素
~: 线程1:【解锁】
*可以看出加锁和解锁的次数是一样的,且删除元素一定在添加元素后调用。