iOS 开发中的锁相关

加锁是实现线程同步方案很重要的一种方式,在iOS中,还是有很多种类型的锁,他们适用不同的场景,当然也存在不同的问题,以下就是各种锁的应用和注意点。


OSSpinLock
自旋锁,目前已经废弃,他叫自旋锁的原因就是因为他在等待加锁的时候,一直处于忙等状态,类似于
while(suo){
}
一直在运行中,判断锁的状态,它存在的问题就是优先级翻转,如果我们设置后进入的线程的优先级较高,那么系统会优先执行这个线程,内部加锁的线程就一直处于等待系统分配时间的状态,也就造成了死锁。
使用

#import 
   OSSpinLock lock = OS_SPINLOCK_INIT;
   OSSpinLockLock(lock);
   OSSpinLockUnlock(lock);
   OSSpinLockTry(lock);

os_unfair_lock
这属于低级锁,在没事的时候处于睡眠状态等待,可以加锁,通过内核通知唤醒,可以代替OSSpinLock使用。

 #import 
    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得形式

你可能感兴趣的:(iOS 开发中的锁相关)