加锁是实现线程同步方案很重要的一种方式,在iOS中,还是有很多种类型的锁,他们适用不同的场景,当然也存在不同的问题,以下就是各种锁的应用和注意点。
OSSpinLock
自旋锁,目前已经废弃,他叫自旋锁的原因就是因为他在等待加锁的时候,一直处于忙等状态,类似于
while(suo){
}
一直在运行中,判断锁的状态,它存在的问题就是优先级翻转,如果我们设置后进入的线程的优先级较高,那么系统会优先执行这个线程,内部加锁的线程就一直处于等待系统分配时间的状态,也就造成了死锁。
使用
#import <libkern/OSAtomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(lock);
OSSpinLockUnlock(lock);
OSSpinLockTry(lock);
os_unfair_lock
这属于低级锁,在没事的时候处于睡眠状态等待,可以加锁,通过内核通知唤醒,可以代替OSSpinLock使用。
#import <os/lock.h>
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&lock);
os_unfair_lock_unlock(&lock);
os_unfair_lock_trylock(&lock);
pthread_mutex
互斥锁,这是跨平台使用的一种锁,比较底层,iOS中很多面向对象的锁NSLock这些,都是封装的这种锁,这种锁,可以设置属性,变成递归锁(允许你同一条线程重复加锁)和条件条件锁。
先看递归锁
//初始化锁属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, 2); //这里的2 就是递归锁
//初始化
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
//尝试加锁
pthread_mutex_trylock(&mutex);
//加锁
pthread_mutex_lock(&mutex);
//解锁
pthread_mutex_unlock(&mutex);
//销毁锁和锁属性
pthread_mutex_destroy(&mutex);
pthread_mutexattr_destroy(&attr);
pthread_mutex_trylock(&mutex); //不会阻塞,而是会继续执行
意思就是,当所被使用了,或者所没有初始化的时候,加锁失败,这个会返回一个int值,除了0都是加锁失败,然后继续执行。
条件锁
pthread_cond_t con;
pthread_cond_init(&con, NULL);
//等待
pthread_cond_wait(&con, &mutex);
//发出可以执行的信号
pthread_cond_signal(&con);
//所有等待整个信号的线程都可以执行了
pthread_cond_broadcast(&con);
是不是很眼熟,可信号量很像
注意使用pthread_mutex,最后都要销毁
NSLock
普通锁,这个锁就是对pthread_mutex普通锁的封装,使用的API
就是
[lock lock]
[lock unLock]
NSRecursiveLock
看名字就知道了,递归锁,允许同一个线程不断加锁,是对pthread_mutex递归锁的封装
一般这种情况下,锁还没解开,你又来了,死锁,但是递归锁允许通一把锁重复加锁。
NSRecursiveLock * recurlock = [[NSRecursiveLock alloc]init];
-(void)lock1{
[recurlock lock];
for (int i = 0; i < 10; i++) {
[self lock1];
}
[recurlock unlock];
NSCondition
条件所,对pthread_mutex的条件锁的简单封装
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
NSConditionLock
是对pthread_mutex条件锁的进一步封装,可以设置具体的数字条件
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@synchronized
这也是对pthread_mutex递归锁的封装,使用起来比较简单
@synchronized (<#token#>) {
<#statements#>
}
但是很多人都说他的性能较差,那么差在什么地方呢?加锁解锁使用的是pthread_mutex,性能不差,差的是寻找锁的过程,根据传入token的不同,生成不同的锁,锁存在一个全局map中,使用了拉链法拓展,所以找锁的过程是消耗性能的地方(另开一篇文章讲讲@synchronized底层的实现)。
其他的线程同步方案
atomic
在set get方法内部添加了自旋锁,保证线程同步,但是影响一定的性能
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
信号量
当我们设置信号量通过线程数量为1 的时候,也是能实现线程同步的。
以上都是一些线程同步的方案,大部分是锁的实现方式。
关于锁,我们看到有自旋锁、互斥锁、递归锁,他们的分类是怎么样的呢?
目前上边介绍的大类就是自旋锁和互斥锁;
自旋锁就是不断访问锁,询问锁是否可用,占用CPU资源。
互斥锁分为递归锁和非递归锁,递归锁允许同一条线程重复加锁,也就是同一条线程,只是对锁的引用计数++,还是会继续执行。
互斥锁维护一个队列,如果锁被使用,将线程加入队列,等待唤醒。
关于锁的性能,参考其他人的测试如下
- OSSpinLock
- dipatch_semaphore
- prethad_mutex
- NSLock
- NSCondition
- pthread_mutex(recursice)
- NSRecuresiceLock
- NSConditionLock
- @synchronized
拓展一点其他,在多线程开发中,一般都是多读单写的要求;实际的场景,买票;查询有多少票的时候可以多条线程查,但是要改变票的数量的时候,只能有一个线程来改,并且改的时候,就不能查了。
实现这个功能,一般有 读写锁、栅栏函数等。
读写锁
读
pthread_rwlock_rdlock(&_lock);
pthread_rwlock_unlock(&_lock);
写
pthread_rwlock_wrlock(&_lock);
pthread_rwlock_unlock(&_lock);
栅栏函数
dispatch_barrier_async
他的实现原理,就是操作队列,当前任务block执行完之前,后续block 不会执行,dispatch_barrier_async前边的block会并发执行。
使用这个注意:要自己建一条队列,否则就是async得形式