一、定义
- mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
- 需要导入头文件#import <pthread.h>
解释:
pthread 开头的都是跨平台的,一般是通用的
* 可以在 windows, Linux ,mac 等
* 如果在 iOS 上会用这个锁,那么在其他平台也会使用这把锁。
* 它是互斥锁:mutex
现在有两种锁:
* 互斥锁
* 自旋锁
二、代码演示,静态初始化
// 初始化锁
- (instancetype)init
{
if (self = [super init]) {
/// 初始化锁
/// 但是当 使用属性赋值时,会报错。
/// 是静态初始化,在定义变量的同时,赋值
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
}
return self;
}
- 创建属性
@property(nonatomic,assign) pthread_mutex_t mutex;
,赋值 属性self.mutex = PTHREAD_MUTEX_INITIALIZER;
会报错。 - 点进
PTHREAD_MUTEX_INITIALIZER
查看#define PTHREAD_MUTEX_INITIALIZER {_PTHREAD_MUTEX_SIG_init, {0}}
- 试试看可不可以 把结构体给 属性赋值 ?
self.mutex = {_PTHREAD_MUTEX_SIG_init, {0}};
不可以。会报错。 self.mutex = *** ;
其实就是self 调用 setMutex[self setMutex:***];
- 定义结构体的时候必须要在定义的同时,赋值
结构体赋值
正确写法:
struct Date {
int year;
int month;
};
// 2011 给 year,10给month
struct Date date = {2011,10};
不能写成
struct Date date;
date = {2011,10};
这是结构体语法规范
三、初始化
- 初始化属性
/// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
- 初始化锁
/// 初始化锁
pthread_mutex_init(&_mutex, &attr);
- 设置锁的类型
- pthread_mutexattr_settype(&attr, 写下面的 0,1,2,)
/*
* Mutex type attributes
*/
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
-
PTHREAD_MUTEX_NORMAL
和PTHREAD_MUTEX_DEFAULT
都是普通锁 -
PTHREAD_MUTEX_ERRORCHECK
用来检查错误的 -
PTHREAD_MUTEX_RECURSIVE
递归锁 -
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
-
销毁属性
/// 销毁属性
pthread_mutexattr_destroy(&attr);
以上代码,代表初始化完一把 锁
全部代码为:
- (instancetype)init
{
if (self = [super init]) {
/// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT)
/// 初始化锁
pthread_mutex_init(&_mutex, &attr);
/// 销毁属性
pthread_mutexattr_destroy(&attr);
}
return self;
}
剩下的代码 :
四、细节
细节一:init 时传NULL
- 当写 初始化锁的时候,可以不用写初始化属性,直接传入NULL 。 当为空时,相当于传的是
PTHREAD_MUTEX_DEFAULT
/// 初始化锁
pthread_mutex_init(&_mutex, NULL);
细节二:不用的时候,要销毁
- (void)dealloc {
pthread_mutex_destroy(&_moneyMutex);
pthread_mutex_destroy(&_ticketMutex);
}
五、例子2
一个方法里面调用另一个方法。两个方法都加锁
代码:
- (void)otherTest {
pthread_mutex_lock(&_mutex);
NSLog(@"%s", __func__);
[self otherTest2];
pthread_mutex_unlock(&_mutex);
}
- (void)otherTest2 {
pthread_mutex_lock(&_mutex);
NSLog(@"%s", __func__);
pthread_mutex_unlock(&_mutex);
}
执行结果: 死锁
解决办法:换锁,两个方法不用一把锁。
没有锁的时候,打印是正常的:
- (void)otherTest {
NSLog(@"%s", __func__);
[self otherTest2];
}
- (void)otherTest2 {
NSLog(@"%s", __func__);
}
打印结果:
如果是递归,也会出现死锁的情况
- (void)otherTest {
pthread_mutex_lock(&_mutex);
NSLog(@"%s", __func__);
// 自己调用自己
[self otherTest];
pthread_mutex_unlock(&_mutex);
}
解决办法: 使用递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
改完以后,会一直循环。
修改代码:
递归锁:允许同一个线程对一把锁进行重复加锁。
如果是线程1 、线程2这样,就不是同一个线程。
它会自动判断是否是同一个线程过来加锁。
六、条件
往数组中添加、删除 元素。应该怎么做?
-
添加元素和删除元素的时候,加同一把锁。
-
在 otherTest 方法中,调用这两个方法。先条用删除元素,再调用添加元素。
-
假如:删除元素和添加元素 是在不同线程做的。
-
想要做的效果是:数组中有元素的时候在删除,没有元素就不删除。
-
想要做的效果如下:
第一步:创建一个条件的属性
第二步:初始化条件
第三步:当不需要条件后,要销毁
第四步: 使用条件
解释:
- 如果有一条线程调用 remove 方法,另一条线程调用 add 方法。当A线程调用 remove 方法时,会对mutex 锁进行加锁。当B线程 调用 add 方法时,要对 mutex 锁进行加锁。由于线程A先进行的加锁,线程B就只能等待。
- remove 方法中有 一个判断,如果数组元素为0,那么锁进行等待。这个锁在睡觉的时候,会被放开。
pthread_cond_wait(&_cond,&_mutex);
这句代码在睡觉的时候,会把锁放开。 - 这个时候,线程B中的add方法,就可以拿到mutex这把锁。
- 一旦数组中有元素了,那么应该告诉 remove 方法,让他删除。
- 在 add 方法中 写
pthread_cond_signal(&_cond);
这句代码会唤醒pthread_cond_wait(&_cond,&_mutex);
这句代码。
- 唤醒后,
pthread_cond_wait(&_cond,&_mutex);
代码会给 _mutex 加锁。然后代码执行到remove 方法中的[self.data removeLastObject];
,然后就会解除 remove 方法中的 锁。 - 也就是说
pthread_cond_wait(&_cond,&_mutex);
这个代码,在等待的时候,会 解锁。然后睡觉。当唤醒的时候。会再次加锁。 - 使用场景:线程1 依赖线程2. 一个线程依赖另一个线程。
- 生产者 - 消费者模式
- 一边生产,一边消费。没有生产就没有消费。