iOS中的几种锁的总结,三种开启多线程的方式(GCD、NSOperation、NSThread)

学习内容

欢迎关注我的iOS学习总结——每天学一点iOS:https://github.com/practiceqian/one-day-one-iOS-summary

OC中的几种锁

为什么要引入锁:在多线程编程中,并发会使多个线程在同一时间内争抢同一资源,因此可能造成资源的数据不一致性,为了解决这个问题就引入了锁

自旋锁:自旋锁在访问被加锁资源时,调用者线程不会进行休眠,而是不停循环在那里,等待被锁资源被释放(不断循环的状态也叫做忙等状态)
互斥锁:互斥锁在访问被加锁资源时,调用者线程会进如休眠状态,此时cpu会调用其他的线程工作,直到被加锁资源释放,唤醒休眠线程。
优缺点:自旋锁由于不会引起调用者线程休眠,所以不会进行cpu调度以及时间片轮转等耗时操作,所以如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁,缺点是自旋锁会一直占用cpu,在未获得锁的情况下一直运行,如果不能短时间内获得锁,会使cpu的运行效率降低,同时自旋锁不能实现递归调用
  1. OSSpinLock进行加锁

    • //OSSpinLock自旋锁的初始化
      OSSpinLock _lock = OS_SPINLOCK_INIT;
      //锁定
      OSSpinLockLock(&_lock);
      //解锁
      OSSpinLockUnlock(&_lock);
      
    • OSSpinLock现在不能继续保证线程安全,因此不建议使用

  2. @synchronized实现加锁

    • //假设共有十张票,有多个线程同时进行买票操作,使用synchronized控制每次只能允许一个线程进行访问
      -(void)saleTickets{
          @synchronized (self) {
              if (self.tickets>0) {
                  self.tickets -= 1;
                  NSLog(@"tickets:%ld---%@",(long)self.tickets,[NSThread currentThread]);
              }else
                  return;
          }
      }
      - (IBAction)clickToPushB:(UIButton *)sender {
          while (self.tickets>0) {
           dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
              [self saleTickets];
            });
          }
      }
      
  3. dispatch_semaphore信号量实现加锁

    • 每当发送一个信号时,信号量加一

    • 每当发送一个等待信号时,信号量减一

    • 如果信号量为0,则信号处于等待状态,直到信号量大于0才开始执行

    • while (self.tickets>0) {
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        [self saleTickets];
        dispatch_semaphore_signal(sema);
        });
      }
      
    • 信号量也能用来控制线程的最大并发数

  4. 使用pthread来进行加锁

    • static pthread_mutex_t pLock;
      pthread_mutex_init(&pLock, NULL);
      while (self.tickets>0) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          pthread_mutex_lock(&pLock);
          [self saleTickets];
          pthread_mutex_unlock(&pLock);
        });
      }
      
    • 加锁后只能有一个线程可以进行某个操作,后面的线程再进行时需要排队,并且lock和unlock是对应出现的,同一个线程不能进行多次lock(递归锁允许其在未释放自己拥有的锁时,反复对该资源进行加锁)

  5. 使用NSLock来进行加锁

    • self.lock = [NSLock new];
      while (self.tickets>0) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          [self.lock lock];
          [self saleTickets];
          [self.lock unlock];
        });
      }
      
  6. 使用NSConditionLock条件锁进行加锁

    • self.conditionLock = [NSConditionLock new];
      while (self.tickets>0) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          [self.conditionLock lock];
          [self saleTickets];
          [self.conditionLock unlock];
        });
      }
      
    • //NSConditonLock条件锁同时也能用来控制线程同步
      self.conditionLock = [NSConditionLock new];
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.conditionLock lock];
        NSLog(@"1");
        [self.conditionLock unlockWithCondition:2];
      
      });
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.conditionLock lockWhenCondition:2];
        NSLog(@"2");
        [self.conditionLock unlock];
      });
      
  7. 使用NSRecursiveLock(递归锁)进行加锁

    • 递归锁主要用在循环或者递归调用中

    • //下面的代码中RecrsiveBlock是递归调用的,如果使用NSLock的话,每次进入RecursiveLock的代码中都会进行一次加锁,但是因为没有解锁,所以需要等待解锁,这样线程就会进入阻塞状态,但是这里使用了NSRecursiveLock就不会进入阻塞状态
      NSRecursiveLock *rLock = [NSRecursiveLock new];
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
          [rLock lock];
          if (value > 0) {
            NSLog(@"线程%d", value);
            RecursiveBlock(value - 1);
          }
          [rLock unlock];
        };
        RecursiveBlock(4);
      });
      
  8. 几种锁的性能对比

    • 锁的性能从高到低依次如下
    • OSSpinLock
    • Dispatch_semaphore
    • Pthread_mutex
    • NSLock
    • NSCondition
    • Pthread_mutex(recursive)
    • NSRecursiveLock
    • NSConditionLock
    • @synchronized

iOS中多线程总结

  1. GCD(Grand Central Dispatch)

    GCD的两个核心概念
    • 队列:这里的队列指的是执行任务的等待队列,即用来存放任务的队列,队列是一种特殊的线性表,采用FIFO(先进先出的)的原则,新任务总是被插入到队列的末尾,而读取任务的时总是从队列的头部开始读取,每读取一个任务,则从队列中释放一个任务
      • 串行队列(serial dispatch queue)
        • 每次只有一个任务被执行,让任务一个接着一个执行(只开启一个线程,一个线程执行完毕后再执行下一个任务)
      • 并发队列
        • 可以让多个任务同时(并发)执行(可以开启多个线程,并发执行任务)
    • 任务:任务就是我们需要执行的操作,即放入队列中的那段代码,在GCD中任务是放在block中执行的,执行任务有两种方式,同步执行(sync),异步执行(async),两者的主要区别是,是否等待队列的任务执行结束,以及是否具备开启新线程的能力
      • 同步任务(sync)
        • 同步添加任务到执行的队列中,在添加的任务执行结束之前,会一直等待,知道队列里面的任务完成之后再继续执行
        • 只能在当前线程中执行任务,不具备开启新线程的能力
      • 异步任务(async)
        • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
        • 可以在新的线程中执行任务,具备开启新线程的能力(但是并不一定会开启新线程,这与指定的队列有关)
    • 任务和队列不同组合方式的区别

    • 并发队列 串行队列 主队列
      同步任务(sync) 不开启新线程,串行执行 不开启新线程,串行执行 不开启新线程,造成死锁
      异步任务(async) 开启新线程,并发执行 开启一条新线程,串行执行 不开启新线程,串行执行任务
    • 队列嵌套的情况下,不同组合方式的区别

      • 在异步串行队列中增加同一个队列的串行同步任务同样也会造成死锁,原理和主队列同步任务是相同的

      • dispatch_queue_t serial_queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
        dispatch_async(serial_queue, ^{
        NSLog(@"serial_queue:%@",[NSThread currentThread]);
        dispatch_sync(serial_queue, ^{
        NSLog(@"serial_queue_sync:%@",[NSThread currentThread]);
        });
        });
        
      • 主队列同步任务造成死锁的原因:在主队列追加同步任务,当执行到同步任务时,主队列中剩下的任务需要等待追加任务的完成才能继续往下执行,但是追加的任务需要等待主队列中的任务按顺序往下执行完才能继续执行。(主队列中追加的任务和主队列中本身的任务相互等待,最终造成死锁)

    • 在iOS中UI的更新必须在主线程中执行

      • dispatch_async(dispatch_get_main_queue(), ^{
          [self.view addSubview:self.scrollView];
        });
        
  2. 使用NSOperation实现多线程

    NSOperation是个抽象类,并不具备封装操作的能力,所以我们需要使用它们的子类
    • NSBlockOperation

      • NSLog(@"1---%@",[NSThread currentThread]);
        NSBlockOperation* opeartion = [NSBlockOperation new];
        [opeartion addExecutionBlock:^{
          NSLog(@"2---%@",[NSThread currentThread]);
        
        }];
        [opeartion addExecutionBlock:^{
          NSLog(@"4---%@",[NSThread currentThread]);
        }];
        NSLog(@"3---%@",[NSThread currentThread]);
        [opeartion start];
        -----------------------------------------------------
        1---<NSThread: 0x6000014d0a00>{number = 1, name = main}
        3---<NSThread: 0x6000014d0a00>{number = 1, name = main}
        2---<NSThread: 0x6000014d0a00>{number = 1, name = main}
        4---<NSThread: 0x6000014a1140>{number = 6, name = (null)}
        
      • 当NSBlockOperation中只有一个任务的话是同步执行,当使用addExecutionBlock:添加了多个任务的话,那么就会默认开启新的线程

    • NSInvocationBlock

      • //NSInvocationOperation默认是在当前线程进行同步执行的,除非自定义额外线程执行Operation
        NSInvocationOperation* invokeOp = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
        [invokeOp start];
        -------------------------------------------------------------------
        //在异步并发队列中增加NSOperation的操作,此时就是异步执行
        NSInvocationOperation* invokeOp = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          [invokeOp start];
        });
        
    • NSOperationQueue

      • 将NSoperation操作(NSBlockOperation和NSInvocationOperation)添加到NSOperationQueue中默认是开启了多线程操作,除了添加这两种封装的操作对象外,NSOperationQueue自己也可以添加操作

        • //操作队列自身添加操作,同样也是异步执行的
          NSOperationQueue* queue = [NSOperationQueue new];
          [queue addOperationWithBlock:^{
          	NSLog(@"operationQueue");
          }];
          
      • NSOperationQueue中的操作可以添加依赖,指定任务执行的顺序,同时也可以指定开启的最大线程数量

        • queue.maxConcurrentOperationCount = 1;
          [op1 addDependency:op2];
          [queue addOperation:op2];
          [queue addOperation:op1];
          
    • 可以自定义子类继承自NSoperation,但是需要实现内部相应的方法

  3. 使用NSThread实现多线程

    • //创建方式一,能手动拿到线程对象,但是需要手动启动线程(start)
      NSThread* thread = [[NSThread alloc]initWithBlock:^{
        NSLog(@"threadTest---%@",[NSThread currentThread]);
      }];
      [thread start];
      //创建方式二,自启动线程,但是无法拿到线程对象
      [NSThread detachNewThreadWithBlock:^{
        NSLog(@"threadTest---%@",[NSThread currentThread]);
      }];
      //创建方式三,自启动线程,不能拿到线程对象
      [self performSelectorInBackground:@selector(saleTickets) withObject:nil];
      //方式四,自定义线程继承自NSThread,需要重写main方法
      ....
       
      //指定操作,指定线程,waitUntilDone(是否等指定的操作完成之后再进行后面的任务)
      [self performSelector:@selector(saleTickets) onThread:[NSThread mainThread] withObject:nil waitUntilDone:false];
      

版权声明:本文为chenprice原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/chenprice/p/12879379.html