开发中引入了异步和多线程的来提高程序性能,也就意味着线程安全成为了多线程的一个障碍,因此线程锁应运而生,而锁如果用不好,还会造成死锁的风险
下面就介绍ios中常用的几种锁,以及读写锁的实现
常见的多线程锁
ios中常见的几种锁包括OSSpinLock、信号量(Semaphore)、pthread_mutex、NSLock、NSCondition、NSConditionLock、pthread_mutex(recursive)、NSRecursiveLock、synchronized
如下所示,为前辈们测试锁性能的案例图(实际可能会略有偏差):
由于OSSpinLock目前已经不再安全,这里就放弃介绍
我们再选锁的时候,如果只是使用互斥锁的效果,那么按照性能排序选择靠前的即可,如果需要锁的一些其他功能,那么根据需要选择,不必过于局限于性能,毕竟实现功能与项目的维护也是非常重要的
其他锁的使用如下所示
信号量(semaphore)
信号量实现加锁功能与其他的略有不同,其通过一个信号值来决定是否阻塞当前线程
wait操作可以使得信号量值减少1,signal使得信号量值增加1
当wait操作使得信号量值小于0时,则所在线程阻塞阻塞休眠,使用signal使得信号量增加时,会顺序唤醒阻塞线程,以此便可以实现加锁功能,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| - (void)semaphore { _semaphore = dispatch_semaphore_create(1); }
- (void)semaphoreUpdate { dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
_money++;
dispatch_semaphore_signal(_semaphore); }
|
pthread互斥锁
pthread互斥锁是 pthread
库中的一员,linux
系统中中常用的库,使用时需要手动import导入 #import <pthread/pthread.h>
其中有 pthread_mutex_trylock
为尝试加锁,如果没被加锁,则会加锁成功,并返回0,适用于一些优先级比较低,间歇性调用的功能
注意:其他部分锁也有trylock
这个功能,例如 NSLock、NSRecursiveLock、NSConditionLock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #pragma mark --pthread互斥锁 - (void)pthreadMutex { pthread_mutex_init(&_pMutexLock, NULL);
}
- (void)pthreadMutexUpdate { pthread_mutex_lock(&_pMutexLock); _money++; pthread_mutex_unlock(&_pMutexLock); }
- (void)pthreadMutexSub { [NSThread detachNewThreadWithBlock:^{ while (self->_money > 10000) { if (pthread_mutex_trylock(&self->_pMutexLock) == 0) { self->_money--; pthread_mutex_unlock(&self->_pMutexLock); }else { [NSThread sleepForTimeInterval:1]; } } }]; }
|
NSLock互斥锁
NSLock 遵循 NSLocking协议
,是常见的互斥锁之一,为 OC 框架中的 API,使用方便,据说是 pthread 封装的锁
tryLock
方法也是尝试加锁,成功返回true,失败返回false
lockBeforeDate:(NSDate *)limit
在一个时间之间加锁,可以理解为加锁日期截止到指定时间,会自动解锁(与信号量的等待功能一样,这个是设置到指定时间)
1 2 3 4 5 6 7 8 9 10 11 12
| #pragma mark --NSLock互斥锁 - (void)NSLock { _lock = [[NSLock alloc] init]; }
- (void)NSLockUpdate { [_lock lock]; _money++; [_lock unlock]; }
|
NSCondition锁
NSCondition 算是一个稍微重量级的锁了,我理解为情景锁
(另一个原因区分条件锁 NSConditionLock
),适用于一些特殊场景,其也遵循 NSLocking
协议,也属于互斥锁
并且再其基础上,新增了信号量功能 wait
和 signal
,即 等待 和 释放 ,使用方式和 semaphore
一样,可以通过信号量控制线程的阻塞和释放,除此之外,还多了一个broadcast
,其可以解除所有因 wait 阻塞的线程
如下所示,使用 NSCondition
实现了一个生产者和消费者的案例(生产者和消费者都是同一拨人,因此需要加锁来实现,而为了保证有钱了立刻买自己想买的东西,使用信号量,保证没钱时阻塞等待,有钱时立即解放买买买)
其相当于同时使用了NSLock 和 Semaphore
功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #pragma mark --情景锁NSCondition实现了NSLocking协议,支持默认的互斥锁lock、unlock - (void)NSCondition { _condition = [[NSCondition alloc] init]; }
- (void)conditionPlusMoney { [_condition lock]; if (_money++ < 0) { [_condition signal]; } [_condition unlock]; }
- (void)conditionSubMoney { [_condition lock]; if (_money == 0) { [_condition wait]; } _money--; [_condition unlock]; }
|
NSConditionLock
NSConditionLock 被称为条件锁,其遵循 NSLocking
协议,即具备正常的互斥锁功能
此外加入了 条件语句,为其核心功能,即满足指定条件才会解锁,因此算是一个重量级的锁了,其同时可以理解为 NSCondition 进化版
,如果你理解了 NSCondition
的生产者-消费者
模式,这个也会马上就明白了其原理了
lockWhenCondition:(NSInteger)condition
: 加锁,当条件condition为传入的condition时,方能解锁
unlockWithCondition:(NSInteger)condition
: 更新condition的值,并解锁指定condition的锁
下面使用一个异步队列,来实现类似 NSOperation 设置的依赖关系,如下所示(打印结果1、4、3、2):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #pragma mark --条件锁NSConditionLock,实现了NSLocking协议,支持默认的互斥锁lock、unlock - (void)NSConditionLock { _conditionLock = [[NSConditionLock alloc] initWithCondition:1]; }
- (void)NSConditionLockUpdate { dispatch_queue_t queue = dispatch_queue_create("测试NSConditionLock", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ if ([self->_conditionLock tryLockWhenCondition:1]) { NSLog(@"第一个"); [self->_conditionLock unlockWithCondition:4]; }else { [self->_conditionLock lockWhenCondition:0]; NSLog(@"第一个other"); [self->_conditionLock unlockWithCondition:4]; } }); dispatch_async(queue, ^{ [self->_conditionLock lockWhenCondition:2]; NSLog(@"第二个"); [self->_conditionLock unlockWithCondition:1]; }); dispatch_async(queue, ^{ [self->_conditionLock lockWhenCondition:3]; NSLog(@"第三个"); [self->_conditionLock unlockWithCondition:2]; }); dispatch_async(queue, ^{ [self->_conditionLock lockWhenCondition:4]; NSLog(@"第四个"); [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_mutex(recursive)
其为基于 pthread框架
的递归锁,也是以 pthread互斥锁为基础
实现的 递归锁
,即:同一个线程下,递归调用时加锁,不会阻塞当前线程,当另一个线程到来时,会因为第一个线程加的锁而阻塞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #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);
}
- (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); }); }
|
NSRecursiveLock递归锁
和 pthread_mutex(recursive)
一样,NSRecursiveLock 也是递归锁,其遵循 NSLocking
协议,即除了递归锁功能,还具备正常的互斥锁功能
使用方式和 pthread_mutex(recursive)
一样如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
- (void)NSRecursiveLockUpdate { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^recursiveBlock)(double count); recursiveBlock = ^(double count){ [self->_recursive lock]; if (count-- > 0) { self->_money++; recursiveBlock(count); } [self->_recursive unlock]; }; recursiveBlock(1000); }); }
|
synchronized
synchronized 同步锁,即同步执行,以此避免多线程同时操作同一块代码,基本上在各个平台都会有其身影,虽然效率最低,但由于使用使用简单,深得大家喜爱
实现如下所示
1 2 3 4 5 6 7 8
| #pragma mark --同步锁synchronized - (void)synchronized { @synchronized (self) { self->_money++; } }
|
读写锁
读写锁
又被称为 rw锁
或者 readwrite锁
,在 ios开发中虽能见到,但确不是最常用的(一般是数据库操作才会用到)。
具体操作为:多读单写
,即,写入操作只能串行执行,且写入时,不能读取,而读取需支持多线程操作,且读取时,不能写入
相信大家也遇到过这样的事,系统的属性设置了 auto
参数,字面意思为原子性操作,其实际未能保证属性字段的多线程安全(由于旧值的赋值未加锁,同时写入时,会造成对象旧地址多次被release)
因此无论是想了解其实现方式,还是开发备用,都是有比较学习的
实现方式这里就提供两种:pthread、GCD的barrier来实现
pthread读写锁
使用前,需要先导入 pthread框架
, 即 #import <pthread/pthread.h>
实现简单,可以根据自己程序需要,选择锁初始化的合适位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| - (void)setupPhreadRW { pthread_rwlock_init(&_lock, NULL); }
#pragma mark --通过pthread读写锁来设置 - (void)setLock1:(NSString *)lock1 { pthread_rwlock_wrlock(&_lock); _lock1 = lock1; pthread_rwlock_unlock(&_lock);
} - (NSString *)lock1 { NSString *lock1 = nil; pthread_rwlock_rdlock(&_lock); lock1 = [_lock1 copy]; pthread_rwlock_unlock(&_lock); return lock1; }
|
GCD的barrier读写锁
GCD的barrier栅栏功能相信大家都听说过,即在一个新创建的队列中,barrier功能可以保证,在他之前的异步队列执行完毕才指定barrier中间的内容,且还能保证barrier执行完毕后,才之后barrier之后的任务,且一个队列可以有多个barrier
因此此特性可以用于完成一个读写锁功能,即 barrier的代码块作为 写入操作模块
如下代码所示,由于需要引入 新创建队列,虽然使用起来不是不如pthread优秀,但这种思想却可以再恰当的时候发芽出新树苗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| - (void)setupGCDRW { _queue = dispatch_queue_create("RWLockQueue", DISPATCH_QUEUE_CONCURRENT); }
#pragma mark --通过GCD的barrier栅栏功能实现
- (void)setLock2:(NSString *)lock2 { dispatch_barrier_async(_queue, ^{ self->_lock2 = lock2; }); } - (void)getLock2WithBlock:(void(^)(NSString *))block { dispatch_async(_queue, ^{ block(self->_lock2); }); }
|