两种锁的加锁原理
特殊的互斥锁,加了递归功能
ios中常见的几种锁包括OSSpinLock、信号量(Semaphore)、pthread_mutex、NSLock、NSCondition、NSConditionLock、pthread_mutex(recursive)、NSRecursiveLock、synchronized
如下所示,测试锁性能的案例图(实际可能会略有偏差):
由于OSSpinLock目前已经不再安全,存在bug,官方已放弃,iOS10之后os_unfair_lock取代OSSpinLock。
OS_UNFAIR_LOCK_INIT 初始化锁。
os_unfair_lock_lock 加锁。参数为os_unfair_lock地址。
os_unfair_lock_unlock 解锁。参数为os_unfair_lock地址。
os_unfair_lock_trylock 尝试加锁。参数为os_unfair_lock地址。如果成功返回true。如果锁已经被锁定则返回false。
os_unfair_lock_assert_owner 参数为os_unfair_lock地址。如果当前线程未持有指定的锁或者锁已经被解锁,则触发崩溃。
os_unfair_lock_assert_not_owner 参数为os_unfair_lock地址。如果当前线程持有指定的锁,则触发崩溃。
基本用法
//创建锁,
self.unfairLock = OS_UNFAIR_LOCK_INIT;
#pragma mark -- os_unfair_lock
-(void)unfairLock_test {
os_unfair_lock_lock(&_unfairLock);
self.count --;
NSLog(@"%d",self.count);
os_unfair_lock_unlock(&_unfairLock);
}
/*! @abstract Data type for a spinlock.
@discussion
You should always initialize a spinlock to {@link OS_SPINLOCK_INIT} before
using it.
*/
typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock);
dispatch_semaphore 是 GCD 用来同步的一种方式,与他相关的只有三个函数,一个是创建信号量,一个是等待信号,一个是发送信号。
dispatch_semaphore_create(long value);
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
dispatch_semaphore 的核心是 dispatch_semaphore_t 类型的信号量。 dispatch_semaphore_create(1) 方法可以创建一个 dispatch_semaphore_t 类型的信号量,设定信号量的初始值为 1。注意,这里的传入的参数必须大于或等于 0,否则 dispatch_semaphore_create 会返回 NULL。 dispatch_semaphore_wait(signal, overTime); 方法会判断 signal 的信号值是否大于 0。大于 0 不会阻塞线程,消耗掉一个信号,执行后续任务。如果信号值为 0,该线程会和 NSCondition 一样直接进入 waiting 状态,等待其他线程发送信号唤醒线程去执行后续任务,或者当 overTime 时限到了,也会执行后续任务。 dispatch_semaphore_signal(signal); 发送信号,如果没有等待的线程接受信号,则使 signal 信号值加一(做到对信号的保存)
//如果信号量值<0,那么该函数就会一直等待(相当于阻塞当前线程)
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务1");
dispatch_semaphore_signal(sem);//+1不在阻塞线程继续执行
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//阻塞不在向下执行代码,阻塞到当前
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务2");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);// 阻塞当前一下代码,等待上面
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
pthread互斥锁是 pthread
库中的一员,linux
系统中中常用的库,使用时需要手动import导入 #import
,其中有 pthread_mutex_trylock
为尝试加锁,如果没被加锁,则会加锁成功,并返回0,适用于一些优先级比较低,间歇性调用的功能
注意:其他部分锁也有trylock
这个功能,例如 NSLock、NSRecursiveLock、NSConditionLock
pthread_mutex_init(pthread_mutex_t mutex,const pthread_mutexattr_t attr)初始化锁,pthread_mutexattr_t可用来设置锁的类型。
pthread_mutex_lock(pthread_mutex_t mutex);//加锁
pthread_mutex_trylock(*pthread_mutex_t *mutex);//加锁,但是上面方法不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待,成功返回0.失败返回错误信息
pthread_mutex_unlock(pthread_mutex_t *mutex);//释放锁
pthread_mutex_destroy(pthread_mutex_t* mutex);//使用完锁之后释放锁
pthread_mutexattr_setpshared();//设置互斥锁的范围
pthread_mutexattr_getpshared() //获取互斥锁的范围
//非递归
pthread_mutex_t lock0;
pthread_mutex_init(&lock0, NULL);
pthread_mutex_lock(&lock0);
pthread_mutex_unlock(&lock0);
pthread_mutex_destroy(&lock0);
//递归
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
pthread_mutex_destroy(&lock);
NSLock 遵循 NSLocking协议
,是常见的互斥锁之一,为 OC 框架中的 API,使用方便,据说是 pthread 封装的锁
- (void)lock 加锁。
- (void)unlock 解锁。
- (BOOL)tryLock 尝试加锁。成功返回YES,失败返回NO。
- (BOOL)lockBeforeDete:(NSDate *)limit 在指定时间点之前获取锁,能够获取返回YES,获取不到返回NO。
@property (nullable ,copy) NSString *name 锁名称。
[self.iLock lock];
self.count --;
NSLog(@"%d",self.count);
[self.iLock unlock];
NSCondition 算是一个稍微重量级的锁了,我理解为情景锁
(另一个原因区分条件锁 NSConditionLock
),适用于一些特殊场景,其也遵循 NSLocking
协议,也属于互斥锁
- (void)lock 加锁。
- (void)unlock 解锁。
- (void)wait 阻塞当前线程,使线程进入休眠,等待唤醒信号。调用前必须已加锁。
- (void)waitUntilDate 阻塞当前线程,使线程进入休眠,等待唤醒信号或者超时。调用前必须已加锁。
- (void)signal 唤醒一个正在休眠的线程,如果要唤醒多个,需要调用多次。如果没有线程在等待,则什么也不做。调用前必须已加锁。
- (void)broadcast 唤醒所有在等待的线程。如果没有线程在等待,则什么也不做。调用前必须已加锁。
@property (nullable ,copy) NSString *name 锁名称。
#pragma mark -- NSCondition
- (void)nscondition_test {
for (int i = 0; i < 50; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self lg_production];
});
}
for (int i = 0; i < 100; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self lg_consumption];
});
}
}
- (void)lg_production {
[self.iCondition lock];
self.count ++;
NSLog(@"生产了一个产品,现有产品 : %d个",self.count);
[self.iCondition signal];
[self.iCondition unlock];
}
- (void)lg_consumption {
[self.iCondition lock];
while (self.count == 0) {
[self.iCondition wait];
}
self.count --;
NSLog(@"消费了一个产品,现有产品: %d个",self.count);
[self.iCondition unlock];
}
NSConditionLock 被称为条件锁,其遵循 NSLocking
协议,即具备正常的互斥锁功能
此外加入了 条件语句,为其核心功能,即满足指定条件才会解锁,因此算是一个重量级的锁了,其同时可以理解为 NSCondition 进化版
,如果你理解了 NSCondition
的生产者-消费者
模式,这个也会马上就明白了其原理了
- (void)lock 加锁。
- (void)unlock 解锁。
- (instancetype)initWithCondition:(NSinteger)初始化一个。NSConditionLock对象。
@property(readonly) NSInteger condition 锁的条件。
- (void)lockWhenCondition:(NSInteger)conditio满足条件时加锁。
- (BOOL)tryLock尝试加锁。
- (BOOL)tryLockWhenCondition如果接受对象的condition与给定的condition相等,则尝试获取锁,不足塞线程。
- (void)unlockWithCondition:(NSInteger)condition解锁,重置锁的条件。
- (BOOL)lockBeforDate:(NSDate *)limit在指定时间点之前获取锁。
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit在指定的时间前获取锁。
@property (nullable ,copy) NSString *name 锁名称。
self.iConditionLock = [[NSConditionLock alloc] initWithCondition:3];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.iConditionLock lockWhenCondition:3];
NSLog(@"线程 1");
[self.iConditionLock unlockWithCondition:2];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.iConditionLock lockWhenCondition:2];
NSLog(@"线程 2");
[self.iConditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.iConditionLock lockWhenCondition:1];
NSLog(@"线程 3");
[self.iConditionLock unlockWithCondition:0];
});
执行结果:
2022-06-02 11:06:16.814961+0800 DemoTest2022[32077:1507071] 线程 1
2022-06-02 11:06:16.815228+0800 DemoTest2022[32077:1507072] 线程 2
2022-06-02 11:06:16.815422+0800 DemoTest2022[32077:1507066] 线程 3
#pragma mark --条件锁NSConditionLock,实现了NSLocking协议,支持默认的互斥锁lock、unlock
- (void)NSConditionLock {
_conditionLock = [[NSConditionLock alloc] initWithCondition:1]; //可以更改值测试为0测试结果
//加锁,当条件condition为传入的condition时,方能解锁
//lockWhenCondition:(NSInteger)condition
//更新condition的值,并解锁指定condition的锁
//unlockWithCondition:(NSInteger)condition
}
//多个队列执行条件锁
//通过案例可以看出,通过条件锁conditionLock可以设置线程依赖关系
//可以通过GCD设置一个具有依赖关系的任务队列么
- (void)NSConditionLockUpdate {
//创建并发队列
dispatch_queue_t queue =
dispatch_queue_create("测试NSConditionLock", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
if ([self->_conditionLock tryLockWhenCondition:1]) {
NSLog(@"第一个");
//默认初始conditon位1,所有能走到这里
//然后解锁后,并设置初始值为4,解锁condition设定为4的线程
[self->_conditionLock unlockWithCondition:4];
}else {
[self->_conditionLock lockWhenCondition:0];
NSLog(@"第一个other");
[self->_conditionLock unlockWithCondition:4];
}
});
//由于开始初始化的conditon值为1,所以后面三个线程都不满足条件
//锁定后直到condition调整为当前线程的condition时方解锁
dispatch_async(queue, ^{
//condition设置为3后解锁当前线程
[self->_conditionLock lockWhenCondition:2];
NSLog(@"第二个");
//执行完毕后解锁,并设置condition为1,设置初始化默认值,以便于下次使用
[self->_conditionLock unlockWithCondition:1];
});
dispatch_async(queue, ^{
//condition设置为3后解锁当前线程
[self->_conditionLock lockWhenCondition:3];
NSLog(@"第三个");
//执行完毕后解锁,并设置condition为3,解锁3
[self->_conditionLock unlockWithCondition:2];
});
dispatch_async(queue, ^{
//condition设置为4后解锁当前线程
[self->_conditionLock lockWhenCondition:4];
NSLog(@"第四个");
//执行完毕后解锁,并设置condition为3,解锁3
[self->_conditionLock unlockWithCondition:3];
});
}
上面的流程可以大致简化为下面几步:
1.创建一个异步队列,以便于添加后续的任务依赖
2.逐步添加子任务模块,分别在不同线程中,其有明确的依赖关系,即执行顺序为 1、4、3、2
3.使用 lockWhenCondition:
开始设置依赖,将其任务解锁的条件condition
设置为其特有的condition 号
,以便于解锁
4.执行任务时,如果 NSCondition 中的 condition 参数
,与本线程设置的tCondition
不一样时,阻塞线程,等待 NSCondition 中的 condition
更改为指定值(通过 unlockWithCondition:
更改condition值)解锁
即:默认初始化 condition 为 1,只有 任务1
能够执行,当 任务1
执行 unlockWithCondition:4
时,condition被设置为4, 阻塞的任务4
解锁,同理,任务4
执行完毕后,将 condition 设置为 3 ,任务三解锁,依次类推
5.最终根据设置的依赖关系,分别执行 任务1、任务4、任务3、任务2
其为基于 pthread框架
的递归锁,也是以 pthread互斥锁为基础
实现的 递归锁
,即:同一个线程下,递归调用时加锁,不会阻塞当前线程,当另一个线程到来时,会因为第一个线程加的锁而阻塞
#pragma mark --pthread递归锁
- (void)pthreadMutexRecursive {
//初始化锁的递归功能
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
//互斥锁初始化时,绑定递归锁功能模块
pthread_mutex_init(&_pMutexLock, &attr);
//使用完毕后在合适的地方销毁,例如dealloc
// pthread_mutexattr_destroy(&attr);
// pthread_mutex_destroy(&_pMutexLock);
}
//使用递归锁,递归地时候回不停加锁,如果使用普通的锁早已经形成死锁,无法解脱
//递归锁的存在就是在同一个线程中的锁,不会互斥,只会互斥其他线程的锁,从而避免死锁
- (void)pthreadMutexRecursiveUpdate {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^recursiveBlock)(double count);
recursiveBlock = ^(double count){
pthread_mutex_lock(&self->_pMutexLock);
if (count-- > 0) {
self->_money++;
recursiveBlock(count);
}
pthread_mutex_unlock(&self->_pMutexLock);
};
recursiveBlock(1000);
});
}
和 pthread_mutex(recursive)
一样,NSRecursiveLock 也是递归锁,其遵循 NSLocking
协议,即除了递归锁功能,还具备正常的互斥锁功能
- (void)lock 加锁。
- (void)unlock 解锁。
- (BOOL)tryLock 尝试加锁。成功返回YES,失败返回NO。
- (BOOL)lockBeforeDete:(NSDate *)limit 在指定时间点之前获取锁,能够获取返回YES,获取不到返回NO。
@property (nullable ,copy) NSString *name 锁名称。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^recursiveBlock)(double count);
recursiveBlock = ^(double count){
[self.irecursiveLock lock];
//tryLock就不多介绍了,和Pthread的类似,注意返回值即可
//[self->_recursive tryLock];
if (count-- > 0) {
self.count++;
NSLog(@"%d",self.count);
recursiveBlock(count);
}
[self.irecursiveLock unlock];
};
recursiveBlock(1000);
});
synchronized 同步锁,即同步执行,以此避免多线程同时操作同一块代码,基本上在各个平台都会有其身影,虽然效率最低,但由于使用使用简单,深得大家喜爱
objc_sync_enter
要锁的代码
objc_sync_exit
例子:
#pragma mark --同步锁synchronized
- (void)synchronized {
//使用简单,直接对代码块加同步锁,此代码不会被多个线程直接执行
//可以间接理解为里面的任务被放到了一个同步队列依次执行(实际实现未知)
@synchronized (self) {
self->_money++;
}
}
objc源码:
######### objc_sync_enter
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {//判断对象是否存在
SyncData* data = id2data(obj, ACQUIRE);//从表中取出需要锁的数据
assert(data);
data->mutex.lock();//对数据加锁
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil(); //如果对象不存在,什么事情都不做!
}
return result;
}
######### SyncData
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex; //递归锁
} SyncData;
@synchronized结论:
nil
防止死锁表
的结构存要锁的对象