学习内容

欢迎关注我的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];

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

  1. 【MySQL】锁——查看当前数据库锁请求的三种方法 20

    MySQL提供了查看当前数据库锁请求的三种方法:1. show  full  processlist命令  观察state和info列 2. show engine  innodb status\G ...

  2. 对象的notify方法的含义和对象锁释放的三种情况

    1,notify的含义     (1)notify一次只随机通知一个线程进行唤醒 (2)在执行了notify方法之后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获得该对象锁, 要等到 ...

  3. ASP.NET MVC中使用Unity进行依赖注入的三种方式

    在ASP.NET MVC中使用Unity进行依赖注入的三种方式 2013-12-15 21:07 by 小白哥哥, 146 阅读, 0 评论, 收藏, 编辑 在ASP.NET MVC4中,为了在解开C ...

  4. VC中加载LIB库文件的三种方法

    VC中加载LIB库文件的三种方法 在VC中加载LIB文件的三种方法如下: 方法1:LIB文件直接加入到工程文件列表中   在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中&quo ...

  5. Java-五种线程池,四种拒绝策略,三种阻塞队列(转)

    Java-五种线程池,四种拒绝策略,三种阻塞队列 三种阻塞队列:    BlockingQueue<Runnable> workQueue = null;    workQueue = n ...

  6. iOS --- UIWebView的加载本地数据的三种方式

    UIWebView是IOS内置的浏览器,可以浏览网页,打开文档  html/htm  pdf   docx  txt等格式的文件.  safari浏览器就是通过UIWebView做的. 服务器将MIM ...

  7. 【转】iOS学习之容易造成循环引用的三种场景

    ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都同 ...

  8. 在ASP.NET MVC中使用Unity进行依赖注入的三种方式

    在ASP.NET MVC4中,为了在解开Controller和Model的耦合,我们通常需要在Controller激活系统中引入IoC,用于处理用户请求的 Controller,让Controller ...

  9. ASP.NET MVC2中Controller向View传递数据的三种方式

    转自:http://www.cnblogs.com/zhuqil/archive/2010/08/03/Passing-Data-from-Controllers-to-View.html 在Asp. ...

随机推荐

  1. week homework: 大家来找茬

    上周课程主题为用户体验,每位同学也根据自己使用APP的体验,例举出一些手机或电脑客户端软件的bug或用户体验非常不好的地方: Tianfu: GitHub.com:界面不够直观,有许多功能不知道入口在 ...

  2. 【论文研读】强化学习入门之DQN

    最近在学习斯坦福2017年秋季学期的<强化学习>课程,感兴趣的同学可以follow一下,Sergey大神的,有英文字幕,语速有点快,适合有一些基础的入门生. 今天主要总结上午看的有关DQN ...

  3. Java优秀教程

    1.java中局部变量是在栈上分配的: 2.数组是储存在堆上的对象,可以保存多个同类型变量: 3.在Java语言中,所有的变量在使用前必须声明. 4.局部变量没有默认值,所以局部变量被声明后,必须经过 ...

  4. java多线程3:原子性,可见性,有序性

    概念 在了解线程安全问题之前,必须先知道为什么需要并发,并发给我们带来什么问题. 为什么需要并发,多线程? 时代的召唤,为了更充分的利用多核CPU的计算能力,多个线程程序可通过提高处理器的资源利用率来 ...

  5. Springboot:员工管理之查询员工列表(十(6))

    构建员工controller com\springboot\controller\EmployeeController.java package com.springboot.controller; ...

  6. 【已解决】error setting certificate verify locations报错

    目录 1.问题描述 2.问题分析 3.解决方法 1.问题描述 在公司的电脑上从Github上clone项目的时候git黑窗口报错"error setting certificate veri ...

  7. 关于unix环境高级编程、Linux程序设计两部书浅谈

    unix环境高级编程的术语很多,概念内容,也很多,不过学习概念性质.标准规则类的东西,想必都是这样吧——需要进行拓展的内容很多. Linux程序设计,图文并茂,代码量够足,看起来,感觉难度还可以. l ...

  8. 牛客网机试题-求root(N,k)

    题目描述     N<k时,root(N,k) = N,否则,root(N,k) = root(N',k).N'为N的k进制表示的各位数字之和.输入x,y,k,输出root(x^y,k)的值 ( ...

  9. discuz 自带的地区四级联动调用方法

    首先,DZ提供了专门处理地区信息的函数,在source/function/function_profile.php(第14行)文件中:function profile_setting(){}那么,我们 ...

  10. [Inno Setup] 如何读取命令行输入的参数

    以 /verysilent 为例 [Code] var isVerySilent: Boolean; function InitializeSetup(): Boolean; var j: Integ ...