美文网首页
无标题文章

无标题文章

作者: 张霸天 | 来源:发表于2016-12-22 16:16 被阅读0次

    iOS中的多线程——锁

    题记:虽然有些事情的发生可能是你预料之中的,但是当它真正的发生了的时候,还是很难以接受的,还是需要一点时间,去缓和这种消极的情绪,尽快站起来吧!加油!花了一天半的时间,各种查阅资料,总结了iOS中关于线程锁的知识,希望我能从中学到一些,也希望可以帮到同样有需要的你!(文中如有错误,还请提出,一起交流)

    本文主要介绍:

    • 互斥锁
    • 递归锁
    • 读写锁
    • 自旋锁
    • 分布锁
    • 条件变量
    • 信号量
    • 栅栏
    • 一些常用锁的性能。

    1. 互斥锁(Mutex)

    常用,当一个线程试图获取被另一个线程占用的锁时,它就会被挂起,让出CPU,直到该锁被释放。

    互斥锁的实现方式:

    • @synchronized:实现单例模式
    • NSLock:不能迭代加锁,如果发生两次lock,而未unlock过,则会产生死锁问题。

    1.@synchronized 同步锁

    当然在Objective-C中你还可以用@synchronized指令快速的实现锁:

    //主线程中
    TestObj *obj = [[TestObj alloc] init];
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized(obj){
            [obj method1];
            sleep(10);
        }
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        @synchronized(obj){
            [obj method2];
        }
    });
    

    @synchronized指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程2中的@synchronized(obj)改为@synchronized(other),刚线程2就不会被阻塞,@synchronized指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

    常用于单例模式的设计:

    例程:

    +(instancetype)shareInstance{
      // 1.定义一个静态实例,初值nil
      static TestSynchronized *myClass = nil;
      // 2.添加同步锁,创建实例
      @synchronized(self) {
          // 3.判断实例是否创建过,创建过则退出同步锁,直接返回该实例
          if (!myClass) {
              // 4.未创建过,则新建一个实例并返回
              myClass = [[self alloc] init];
          }
      }
      return myClass;
    }
    

    此时为了保证单例模式的更加严谨,需要重写allocWithZone方法,保证其他开发者使用allocinit方法时,不再创建新的对象。必要的时候还需要重写copyWithZone方法防止copy属性对单例模式的影响。

    iOS中还有一种更加轻便的方法实现单例模式,即使用GCD中的dispatch_once函数实现。

    例程:

    +(instancetype)shareInstance{
        static TestSynchronized *myClass = nil;
        static dispatch_once_t once_token;
        dispatch_once(&once_token, ^{
            myClass = [[self alloc] init];
        });
        return myClass;
    }
    

    2.NSLock

    例程:

    static NSLock *mylock;
    -(void)viewDidLoad {
      [super viewDidLoad];
      mylock = [[NSLock alloc] init];
    }
    -(void)myLockTest1{
      if ([mylock tryLock]) {
          // to do something
          [mylock unlock];
      }
    }
    -(void)myLockTest2{
      [mylock lock];
      // to do something
      [mylock unlock];
    }
    

    2. 递归锁(Recursive Lock)

    递归锁可以被同一线程多次请求,而不会引起死锁,即在多次被同一个线程进行加锁时,不会造成死锁。这主要是用在循环或递归操作中。

    可以允许同一线程多次加锁,而不会造成死锁。

    递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。

    递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。

    NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          static void (^MyRecursiveLockBlk)(int value);
          MyRecursiveLockBlk = ^(int value){
              [myRecursiveLock lock];
              if (value > 0) {
                  // to do something
                  NSLog(@"MyRecursiveLockBlk value = %d", value);
                  MyRecursiveLockBlk(value - 1);
              }
              [myRecursiveLock unlock];
          };
          MyRecursiveLockBlk(6);
      });
    

    此时如果将例程中的递归锁换成互斥锁:

    NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];换成
    NSLock *myLock = [[NSLock alloc] init];,则会发生死锁问题。
    

    3. 读写锁(Read-write Lock)

    读写锁将访问者分为读出和写入两种,当读写锁在读加锁模式下,所有以读加锁方式访问该资源时,都会获得访问权限,而所有试图以写加锁方式对其加锁的线程都将阻塞,直到所有的读锁释放。

    当在写加锁模式下,所有试图对其加锁的线程都将阻塞。

    #import "ViewController.h"
    #import <pthread.h>
    @interface ViewController ()
    @property(nonatomic, copy) NSString *rwStr;
    @end
    @implementation ViewController
    pthread_rwlock_t rwlock;
    -(void)viewDidLoad {
      [super viewDidLoad];
      // 初始化读写锁
      pthread_rwlock_init(&rwlock,NULL);
      __block int i;
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          i = 5;
          while (i>=0) {
              NSString *temp = [NSString stringWithFormat:@"writing == %d", i];
              [self writingLock:temp];
              i--;
          }  
      });
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          i = 5;
          while (i>=0) {
              [self readingLock];
              i--;
          }
      });
    }
    // 写加锁
    -(void)writingLock:(NSString *)temp{
      pthread_rwlock_wrlock(&rwlock);
      // writing
      self.rwStr = temp;
      NSLog(@"%@", temp);
      pthread_rwlock_unlock(&rwlock);
    }
    // 读加锁
    -(NSString *)readingLock{
      pthread_rwlock_rdlock(&rwlock);
      // reading
      NSString *str = self.rwStr;
      NSLog(@"reading == %@",self.rwStr);
      pthread_rwlock_unlock(&rwlock);
      return str;
    }
    @end
    
    

    4. 自旋锁(Spin Lock)

    • 自旋锁与互斥锁类似
    • 但不同的是:自旋锁是非阻塞的,当一个线程无法获取自旋锁时,会自旋,直到该锁被释放,等待的过程中线程并不会挂起。(实质上就是,如果自旋锁已经被别的执行单元保持,调用者就一直循环在等待该自旋锁的保持着已经释放了锁)。
    • 自旋锁的使用者一般保持锁的时间很短,此时其效率远高于互斥锁。

    自旋锁保持期间是抢占失效的

    优点:效率高,不用进行线程的切换

    缺点:如果一个线程霸占锁的时间过长,自旋会消耗CPU资源

    // 头文件
    #import <libkern/OSAtomic.h>
    // 初始化自旋锁
    static OSSpinLock myLock = OS_SPINLOCK_INIT;
    // 自旋锁的使用
    -(void)SpinLockTest{
      OSSpinLockLock(&myLock);
      // to do something
      OSSpinLockUnlock(&myLock);
    }
    

    5. 分布锁(Didtributed Lock)(进程间用的,不是线程)

    • 跨进程的分布式锁,是进程间同步的工具,底层是用文件系统实现的互斥锁,并不强制进程休眠,而是起到告知的作用。
    • NSDistributedLock没有实现NSLocking协议,所以没有会阻塞线程的lock方法,取而代之的是非阻塞的tryLock方法来获取锁,用unlock方法释放锁。
    • 如果一个获取锁的进程在释放锁之前就退出了,那么锁就一直不能释放,此时可以通过breakLock强行获取锁。

    6. 条件变量(Condition Variable)

    • 使用情况:如果一个线程需要等待某一条件出现才能继续执行,而这个条件是由别的线程产生的,这个时候就用到条件变量。常见的情况是:生产者-消费者问题。

    • 条件变量可以让一个线程等待某一条件,当条件满足时,会收到通知。在获取条件变量并等待条件发生的过程中,也会产生多线程的竞争,所以条件变量通常和互斥锁一起工作。

    • NSCondition:是互斥锁和条件锁的结合,即一个线程在等待signal而阻塞时,可以被另一个线程唤醒,由于操作系统实现的差异,即使没有发送signal消息,线程也有可能被唤醒,所以需要增加谓词变量来保证程序的正确性。

    • NSConditionLock:与NSCondition的实现机制不一样,当定义的条件成立的时候会获取锁,反之,释放锁。

    NSCondition的例程:

    // 创建锁
    NSCondition *condition = [[NSCondition alloc] init];
    static int count = 0;
    // 生产者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      while(count<20)
      {
          [condition lock];
          // 生产
          count ++;
          NSLog(@"生产 = %d",count);
          [condition signal];
          [condition unlock];
      }
    });
    // 消费者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      while (count>]]>0)
      {
          [condition lock];
          // 消耗
          count --;
          NSLog(@"消耗剩余 = %d",count);
          [condition unlock];
      }
    });
    

    NSConditionLock的例程:

    // 创建锁 
    NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:ConditionHASNOT];
    static int count = 0; 
    // 生产者
    while(true) {  
      [condLock lock];  
      // 生产   
      count ++;   
      [condLock unlockWithCondition:ConditionHAS]; 
    } 
      // 消费者 
    while (true) {   
      [condLock lockWhenCondition:ConditionHAS];   
      // 消耗   
      count --;   
      [condLock unlockWithCondition:(count<=0 ? ConditionHASNOT : ConditionHAS)];
    }
    

    7. 信号量(Semaphore)

    • 信号量:可以是一种特殊的互斥锁,可以是资源的计数器
    • 可以使用GCD中的Dispatch Semaphore实现,Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。计数为0时等待,计数大于等于1时,减1为不等待。

    8. 栅栏/屏障(Barrier)

    • 栅栏必须单独执行,不能与其他任务并发执行,栅栏只对并发队列有意义。
    • 栅栏只有等待当前队列所有并发任务都执行完毕后,才会单独执行,带起执行完毕,再按照正常的方式继续向下执行。

    iOS中线程锁的性能对比:

    • No1.自旋锁OSSpinLock耗时最少
    • No2.pthread_mutex
    • No3.NSLock/NSCondition/NSRecursiveLock 耗时接近
    • No4.@synchronized
    • No5.NSConditionLock
    • 栅栏的性能并没有很好,在实际开发中也很少用到(笔者在最近一次面试中就遇到,问栅栏的性能怎么样?当时并不知道栅栏在实际应用中的性能并不是很理想,又被问到苹果官方常使用的锁是什么?应该是自旋锁,然而笔者当时还是不知道。。。)

    相关文章

      网友评论

          本文标题:无标题文章

          本文链接:https://www.haomeiwen.com/subject/gincvttx.html