美文网首页
iOS 多线程(四)GCD

iOS 多线程(四)GCD

作者: shuaikun | 来源:发表于2020-12-08 23:46 被阅读0次

    一、GCD简介

        iOS开发中多线程的API主要有pthreadNSThreadNSOperationGCD,前两者在现在开发过程中已经不常用,NSOperation是面向对象封装的一套API,而GCD则是一套纯C语言API。
       GCDGrand Central Dispatch)是Apple开发的一个多核编程的解决方法,它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统,它是一个在线程池模式的基础上执行的并行任务。

    二、Dispatch_Queue(队列)

    1、队列:

        GCD中的队列和数据结构中的队列特性上一致,都是受限制的线性表,遵循FIFO(First In First Out),即新的任务需要在队尾加入,读取任务则从队首部读取,即先进先出
    (1)串行队列(Serial Dispatch Queue
        串行队列就是顺序执行任务,每次执行一个,上一个任务执行完毕后执行下一个任务
    (2)并发队列(Concurrent Dispatch Queue
        并发队列就是同时执行多个任务,需要注意的是,这些任务会按照被添加的顺序依次开始执行。但是任务完成的顺序是任意的

    2、任务:

      任务:在 GCD 里指的是 Block,即一段需要执行的代码块
    (1)同步执行(dispatch_sync)
        完成任务后才会返回,进行下一任务,可见同步不具备开启线程能力,只会在当前线程依次执行
    (2)异步执行(dispatch_async)
        完成任务后立即返回,进行下一任务,具备多线程能力

    注意点:并发队列只会在异步执行下生效,同步执行不会触发多线程创建。

    三、GCD队列编程实现

    1、多线程创建

    (1)创建自定义队列

    /*
    dispatch_queue_create 创建一个 dispatch_queue_t 队列 ,第一个参数设置该队列标识符,用于调试使用,第二个参数,则是队列类型
    DISPATCH_QUEUE_SERIAL串行队列
    DISPATCH_QUEUE_CONCURRENT 并发队列
    */
    //串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    //并发队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    

    (2)系统队列:主线程队列和全局队列

    • 主线程队列:这个是主线程的串行队列
    • 全局队列:这是个全局的并发队列,很多时候可以不需要自己创建并发队列,直接获取全局队列即可 第一个参数为优先级,这是个大概优先级设置
    /**
      DISPATCH_QUEUE_PRIORITY_HIGH //高优先级
      DISPATCH_QUEUE_PRIORITY_DEFAULT //默认优先级
      DISPATCH_QUEUE_PRIORITY_LOW //低优先级
      DISPATCH_QUEUE_PRIORITY_BACKGROUND //后台优先级
    */
    //主线程队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    //全局队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    

    (3)队列中添加任务执行

    //异步执行
    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue中异步执行");
    });
    
    //同步执行
    dispatch_sync(serialQueue, ^{
        NSLog(@"serialQueue中同步执行");
    });
    
    (4)总共为3种队列类型,6种组合方式 WechatIMG96.png
    2、3种类型6种组合方式介绍
    (1)主线程同步执行(死锁)
    代码:
    NSLog(@"Begin");
    //主线程同步同步执行
    dispatch_sync(mainQueue, ^{
      NSLog(@"主线程同步同步执行");
    });
    NSLog(@"End");
    
    运行结果:
    2017-05-03 16:50:31.542 GCDDemo[41489:596607] Begin
    

      只打印了begin,并没有执行下去,事实上这里发生了死锁。首先代码是在主线程执行,主线程是串行队列,而dispatch_sync是同步执行,sync添加的任务需要执行需要等待NSLog(@"End");执行完毕,NSLog(@"End");任务本身也添加在主线程队列中,所以执行这个任务的前提是sync添加的任务执行完毕,这就出现了两个任务互相等待,造成死锁
      总结:主线程中执行同步任务会发生死锁,即:串行队列中嵌套串行队列任务会发生死锁

    (2)主线程异步执行
    代码:
    NSLog(@"Begin"); 
    //主线程异步执行 
    dispatch_async(mainQueue, ^{ 
    for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------主线程异步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_async(mainQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------主线程异步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_async(mainQueue, ^{ 
       for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------主线程异步执行%@", i,[NSThread currentThread]); 
      } 
    }); 
    
    NSLog(@"End"); 
    
    运行结果:
    2017-05-03 17:09:26.031 GCDDemo[42671:613068] Begin
    2017-05-03 17:09:26.031 GCDDemo[42671:613068] End
    2017-05-03 17:09:26.042 GCDDemo[42671:613068] 0---------主线程异步执行<NSThread: 0x60000007d5c0>{number = 1, name = main}
    2017-05-03 17:09:26.043 GCDDemo[42671:613068] 1---------主线程异步执行<NSThread: 0x60000007d5c0>{number = 1, name = main}
    2017-05-03 17:09:26.044 GCDDemo[42671:613068] 2---------主线程异步执行<NSThread: 0x60000007d5c0>{number = 1, name = main}
    2017-05-03 17:09:26.052 GCDDemo[42671:613068] 0---------主线程异步执行<NSThread: 0x60000007d5c0>{number = 1, name = main}
    2017-05-03 17:09:26.057 GCDDemo[42671:613068] 1---------主线程异步执行<NSThread: 0x60000007d5c0>{number = 1, name = main}
    2017-05-03 17:09:26.059 GCDDemo[42671:613068] 2---------主线程异步执行<NSThread: 0x60000007d5c0>{number = 1, name = main}
    2017-05-03 17:09:26.060 GCDDemo[42671:613068] 0---------主线程异步执行<NSThread: 0x60000007d5c0>{number = 1, name = main}
    2017-05-03 17:09:26.062 GCDDemo[42671:613068] 1---------主线程异步执行<NSThread: 0x60000007d5c0>{number = 1, name = main}
    2017-05-03 17:09:26.063 GCDDemo[42671:613068] 2---------主线程异步执行<NSThread: 0x60000007d5c0>{number = 1, name = main}
    

    总结:主线程队列异步执行不会开辟线程,会在当前线程同步执行。

    (3)串行队列同步执行
    代码:
    NSLog(@"Begin"); 
    //串行队列同步执行 
    dispatch_sync(serialQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------串行队列同步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_sync(serialQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------串行队列同步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_sync(serialQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------串行队列同步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    NSLog(@"End");
    
    运行结果: 
    2017-05-03 17:25:48.225 GCDDemo[43694:625941] Begin
    2017-05-03 17:25:48.225 GCDDemo[43694:625941] 0---------串行队列同步执行<NSThread: 0x60800007f880>{number = 1, name = main}
    2017-05-03 17:25:48.226 GCDDemo[43694:625941] 1---------串行队列同步执行<NSThread: 0x60800007f880>{number = 1, name = main}
    2017-05-03 17:25:48.226 GCDDemo[43694:625941] 2---------串行队列同步执行<NSThread: 0x60800007f880>{number = 1, name = main}
    2017-05-03 17:25:48.226 GCDDemo[43694:625941] 0---------串行队列同步执行<NSThread: 0x60800007f880>{number = 1, name = main}
    2017-05-03 17:25:48.227 GCDDemo[43694:625941] 1---------串行队列同步执行<NSThread: 0x60800007f880>{number = 1, name = main}
    2017-05-03 17:25:48.227 GCDDemo[43694:625941] 2---------串行队列同步执行<NSThread: 0x60800007f880>{number = 1, name = main}
    2017-05-03 17:25:48.227 GCDDemo[43694:625941] 0---------串行队列同步执行<NSThread: 0x60800007f880>{number = 1, name = main}
    2017-05-03 17:25:48.228 GCDDemo[43694:625941] 1---------串行队列同步执行<NSThread: 0x60800007f880>{number = 1, name = main}
    2017-05-03 17:25:48.228 GCDDemo[43694:625941] 2---------串行队列同步执行<NSThread: 0x60800007f880>{number = 1, name = main}
    2017-05-03 17:25:48.228 GCDDemo[43694:625941] End
    

      总结: 串行队列异步执行不会开辟多线程,只会在一条线程中依次执行

    (4)串行队列异步执行
    代码:
    NSLog(@"Begin"); 
    //串行队列异步执行 
    dispatch_async(serialQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------串行队列异步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_async(serialQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------串行队列异步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_async(serialQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------串行队列异步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    NSLog(@"End");
    
    运行结果:
    2017-05-03 17:20:16.514 GCDDemo[43349:621375] Begin
    2017-05-03 17:20:16.514 GCDDemo[43349:621375] End
    2017-05-03 17:20:16.515 GCDDemo[43349:621680] 0---------串行队列异步执行<NSThread: 0x608000268040>{number = 3, name = (null)}
    2017-05-03 17:20:16.515 GCDDemo[43349:621680] 1---------串行队列同步执行<NSThread: 0x608000268040>{number = 3, name = (null)}
    2017-05-03 17:20:16.515 GCDDemo[43349:621680] 2---------串行队列异步执行<NSThread: 0x608000268040>{number = 3, name = (null)}
    2017-05-03 17:20:16.516 GCDDemo[43349:621680] 0---------串行队列异步执行<NSThread: 0x608000268040>{number = 3, name = (null)}
    2017-05-03 17:20:16.516 GCDDemo[43349:621680] 1---------串行队列异步执行<NSThread: 0x608000268040>{number = 3, name = (null)}
    2017-05-03 17:20:16.516 GCDDemo[43349:621680] 2---------串行队列异步执行<NSThread: 0x608000268040>{number = 3, name = (null)}
    2017-05-03 17:20:16.516 GCDDemo[43349:621680] 0---------串行队列异步执行<NSThread: 0x608000268040>{number = 3, name = (null)}
    2017-05-03 17:20:16.517 GCDDemo[43349:621680] 1---------串行队列异步执行<NSThread: 0x608000268040>{number = 3, name = (null)}
    2017-05-03 17:20:16.517 GCDDemo[43349:621680] 2---------串行队列异步执行<NSThread: 0x608000268040>{number = 3, name = (null)}
    

      总结: 串行队列异步执行不会开辟多线程,只会在一条线程中依次执行
      串行队列同步和异步执行都没有开辟多线程,在一条线程中同步执行,那么对于串行队列同步和异步执行有什么区别呢?
      区别只有一点:
      dispatch_async:不会阻塞当前队列,立即返回添加当前队列后面任务,可以看到上图打印结果,先打印end。再打印async任务
      dispatch_sync:会阻塞当前队列,等该sync任务全部执行完毕之后再添加当前队列后面任务,可以看到上图打印结果,先打印完sync任务打印end

    (6)并发队列同步执行
    NSLog(@"Begin"); 
    //串行队列同步执行 
    dispatch_sync(concurrentQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------并发队列同步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_sync(concurrentQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------并发队列同步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_sync(concurrentQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------并发队列同步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    NSLog(@"End");
    
    运行结果:
    2017-05-03 17:34:24.407 GCDDemo[44222:633000] Begin 
    2017-05-03 17:34:24.407 GCDDemo[44222:633000] 0---------并发队列同步执行<NSThread: 0x60800006f380>{number = 1, name = main} 
    2017-05-03 17:34:24.407 GCDDemo[44222:633000] 1---------并发队列同步执行<NSThread: 0x60800006f380>{number = 1, name = main} 
    2017-05-03 17:34:24.408 GCDDemo[44222:633000] 2---------并发队列同步执行<NSThread: 0x60800006f380>{number = 1, name = main} 
    2017-05-03 17:34:24.408 GCDDemo[44222:633000] 0---------并发队列同步执行<NSThread: 0x60800006f380>{number = 1, name = main} 
    2017-05-03 17:34:24.408 GCDDemo[44222:633000] 1---------并发队列同步执行<NSThread: 0x60800006f380>{number = 1, name = main} 
    2017-05-03 17:34:24.408 GCDDemo[44222:633000] 2---------并发队列同步执行<NSThread: 0x60800006f380>{number = 1, name = main} 
    2017-05-03 17:34:24.408 GCDDemo[44222:633000] 0---------并发队列同步执行<NSThread: 0x60800006f380>{number = 1, name = main} 
    2017-05-03 17:34:24.408 GCDDemo[44222:633000] 1---------并发队列同步执行<NSThread: 0x60800006f380>{number = 1, name = main} 
    2017-05-03 17:34:24.409 GCDDemo[44222:633000] 2---------并发队列同步执行<NSThread: 0x60800006f380>{number = 1, name = main} 
    2017-05-03 17:34:24.410 GCDDemo[44222:633000] End
    

    总结: 并发队列同步执行不会开辟多线程,只会在一条线程中依次执行

    (6)并发队列异步执行
    NSLog(@"Begin"); 
    //并发队列异步执行 
    dispatch_async(concurrentQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------并发队列异步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_async(concurrentQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------并发队列异步执行%@", i,[NSThread currentThread]); 
      } 
    });
    
    dispatch_async(concurrentQueue, ^{ 
      for (int i = 0; i < 3; i ++) { 
        NSLog(@"%d---------并发队列异步执行%@", i,[NSThread  currentThread]); 
      } 
    });
    
    NSLog(@"End");
    
    运行结果:
    2017-05-03 17:38:10.893 GCDDemo[44461:636216] Begin
    2017-05-03 17:38:10.894 GCDDemo[44461:636216] End
    2017-05-03 17:38:10.894 GCDDemo[44461:636252] 0---------并发队列异步执行<NSThread: 0x60800007a700>{number = 4, name = (null)}
    2017-05-03 17:38:10.894 GCDDemo[44461:636254] 0---------并发队列异步执行<NSThread: 0x60800007ac40>{number = 5, name = (null)}
    2017-05-03 17:38:10.894 GCDDemo[44461:636251] 0---------并发队列异步执行<NSThread: 0x60800007a800>{number = 3, name = (null)}
    2017-05-03 17:38:10.897 GCDDemo[44461:636252] 1---------并发队列异步执行<NSThread: 0x60800007a700>{number = 4, name = (null)}
    2017-05-03 17:38:10.899 GCDDemo[44461:636254] 1---------并发队列异步执行<NSThread: 0x60800007ac40>{number = 5, name = (null)}
    2017-05-03 17:38:10.900 GCDDemo[44461:636251] 1---------并发队列异步执行<NSThread: 0x60800007a800>{number = 3, name = (null)}
    2017-05-03 17:38:10.903 GCDDemo[44461:636252] 2---------并发队列异步执行<NSThread: 0x60800007a700>{number = 4, name = (null)}
    2017-05-03 17:38:10.904 GCDDemo[44461:636254] 2---------并发队列异步执行<NSThread: 0x60800007ac40>{number = 5, name = (null)}
    2017-05-03 17:38:10.905 GCDDemo[44461:636251] 2---------并发队列异步执行<NSThread: 0x60800007a800>{number = 3, name = (null)}
    

      总结: 并发队列异步执行会开辟多线程执行,并且执行顺序不定

    四、应用示例

    异步处理数据完成后,主线程更新UI界面

    dispatch_async(globalQueue, ^{
      //异步数据处理...
      dispatch_async(mainQueue, ^{
        //主线程更新UI
      });
    });
    

      可以看到运用GCD可以轻松的进行线程间通信,使用指定队列进行串行处理任务,例如数据库存储等依赖线程安全等处理

    //串行队列保证线程安全
    dispatch_sync(serialQueue, ^{
      //数据存储等依赖线程安全操作...
    });
    

      FMDBDataBaseQueue就是使用的串行队列来保证线程安全的

    五、其他GCD API

    1、dispatch_after
    dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 10*NSEC_PER_SEC);
      dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"After");
    });
    

      延迟执行,有时候可以作为定时执行作用,需要注意的是,该函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch_queue,实际执行时间受到runloop的状态影响,存在偏差。

    2、dispatch_once
    static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
       sharedManager = [[SchoolManager alloc] init];
     });
    

      执行一次,在block中的代码全局只会执行一次,被广泛用于单例创建中

    3、dispatch_suspend / dispatch_resume
    dispatch_suspend(queque); //挂起队列  
    dispatch_resume(queue);//恢复队列 
    

      挂起对已经执行的任务没有影响,会暂停所有未执行的任务以及后续追加的任务,恢复则会继续执行所有被挂起的任务

    4、dispatch_set_target_queue
    //搬运一段代码
    dispatch_queue_t mySerialDispatchQueue =
    dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
    dispatch_queue_t globalDispatchQueueBackground =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
    

      dispatch_set_target_queue主要有两个作用:
      (1)设置优先级:自建的队列优先级默认和系统队列优先级一致,设置参数1队列的优先级和参数2的优先级一致,显然你不能设置系统全局队列和主队列优先级
      (2)更改队列的执行层级:如果多个串行队列设置函数目标串行队列是某一个串行队列,原本并发执行的串行队列,在目标串行队列上只能依次执行,代码示例如下

    dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
    
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    dispatch_async(queue1, ^{
      NSLog(@"1 in");
      [NSThread sleepForTimeInterval:3.f];
      NSLog(@"1 out");
    });
    
    dispatch_async(queue2, ^{
      NSLog(@"2 in");
      [NSThread sleepForTimeInterval:2.f];
      NSLog(@"2 out");
    });
    
    dispatch_async(queue3, ^{
      NSLog(@"3 in");
      [NSThread sleepForTimeInterval:1.f];
      NSLog(@"3 out");
    });
    
    5、dispatch_group

      主要应对这样的需求,异步处理完A和B任务,两者都执行完执行C任务,和NSOperation中的依赖一致。示例如下

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      //异步耗时操作A
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      //异步耗时操作B
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
      // 前面的异步操作A和B都执行完之后,回主线程
    });
    
    6、dispatch_apply

      这是dispatch_syncdispatch_group的关联API,按指定次数将指定的Block追加到指定的Dispatch_Queue中,并且等待全部执行结束。可以用于遍历效果

    //全局队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, globalQueue, ^(size_t index) {
      //重复执行10次
      NSLog(@"%zu",index);
    });
    //10次执行完之后,再执行Done
    NSLog(@"Done");
    
    6、dispatch_barrier_sync / dispatch_barrier_async

      栅栏:有时候创建两组并发任务,如果在中间加入栅栏,那么这个任务会在第一组任务完成后执行,并且第二组任务会在栅栏任务完成后才开始执行,如下图所示在并发队列中添加任务,执行顺序一定是:任务组A -> Barrier任务 -> 任务组B,
    示例代码如下:

    dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
      NSLog(@"1---------");
    });
    dispatch_async(concurrentQueue, ^{
      NSLog(@"2--------");
    });
    dispatch_barrier_async(concurrentQueue, ^{
      NSLog(@"barrier--------");
    });
    dispatch_async(concurrentQueue, ^{
      NSLog(@"3--------");
    });
    dispatch_async(concurrentQueue, ^{
      NSLog(@"4--------");
    });
    

      打印执行顺序1,2不定,3,4也不定,但是barrier一定在1和2之后,3和4一定在barrier之后,可以自行添加数量测试。
      应用场景,经常我们会自行创建一个队列进行文件读取和存储,一般文件读取的速度很快,可以使用并发队列多线程提高读取效率,但是文件存储需要考虑到线程安全,那么我们就可以使用barrier进行文件存储操作,类似这样

    dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
      //文件读取
    });
    dispatch_async(concurrentQueue, ^{
      //文件读取
    });
    dispatch_barrier_async(concurrentQueue, ^{
      //文件存储
    });
    dispatch_barrier_async(concurrentQueue, ^{
      //文件存储
    });
    dispatch_async(concurrentQueue, ^{
      //文件读取
    });
    

      可见使用barrier可以轻松高效的实现文件IO。
      dispatch_barrier需要注意的点,dispatch_barrier只会对自建的队列生效,对于系统的mainQueueGlobalQueue不起作用
      dispatch_barrier_asyncdispatch_barrier_sync的区别也同样在于同步和异步,dispatch_barrier_async不会等待自己任务执行完毕才会在队列中添加其他任务,而dispatch_barrier_sync会等待自己任务执行完毕后才会在队列中添加其他任务。
      AFNetworking中大量使用dispatch_barrier_async做数据存储,可以看到dispatch_barrier_async也可以实现串行同步队列效果,相比于dispatch_sync容易产生死锁(在串行队列中同步添加该串行队列任务即会发生死锁),dispatch_barrier_async更加安全。

    五、Dispatch Semaphore 信号量

      dispatch_semaphore_t信号量本质上是一种锁。
      下面我们看下信号量的使用:dispatch_semaphore_t的作用之一解决资源抢夺问题
      对于数据存储类似数据库,非原子性可变字典和可变数组等多线程下不安全的操作,可以使用同步队列保证线程安全,那么在并发队列中,可以使用信号量来解决资源抢夺问题

    //全局队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    //创建一个信号量,初始值为1 
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1) ; //创建可变数组 
    NSMutableArray *array = [[NSMutableArray alloc] init];
     for  (int i = 0; i< 1000; ++i)  { 
      dispatch_async(queue, ^{ 
        //这里会一直等待,直到信号量大于等于1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) ; 
        //执行到这里,消费一个信号量 
        NSLog(@"%@",[NSThread currentThread]);
        [array addObject:[NSNumber numberWithInt:i]]; 
        //这里增加一个信号量 
        dispatch_semaphore_signal(semaphore); 
        }); 
    }
    

    代码解读:
      dispatch_semaphore_create(1) 创建了值为1信号量
      dispatch_semaphore_wait ,如果信号量的值大于等于1,那么,信号量值减1,然后向下执行,如果信号量值为0,一直等待。直到大于等于1的时候,率先进入等待状态的异步队列率先执行
      dispatch_semaphore_signal信号量值加1
      实际这种效果和加锁的本质一致,dispatch_semaphore_t的另外一个作用就是可以控制线程并发数量,iOS7之后系统自动开辟的线程数量可以多达60-70,而GCD中并没有提供控制线程数量的API,NSOperation中可以设置最大线程数。

      下面我们使用信号量来实现一下线程数量控制:

    //线程并发数限制 
    static dispatch_semaphore_t limitSemaphore; 
    //控制专用队列 
    static dispatch_queue_t serialQueue; 
    //单例创建 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{
      //设置最大线程并发数为5 
      limitCount = dispatch_semaphore_create(5);
      serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    }); 
    dispatch_async(serialQueue, ^{ 
      //信号量>=1继续执行,否则等待 
      dispatch_semaphore_wait(limitSemaphore, DISPATCH_TIME_FOREVER);
      dispatch_async(queue, ^{
        //这里执行一些任务 
        NSLog(@"%@",[NSThread currentThread]);
        //在该工作线程执行完成后释放信号量
        dispatch_semaphore_signal(limitSemaphore);
        }); 
    });
    

    六、dispatch source

      dispatch source是一组不常用的GCD API。是BSD系内核惯有功能kqueue的包装。简单来说,dispatch source是一个监视某些类型事件的对象。它支持所有kqueue所支持的事件以及mach(mach介绍可以看这里mach wikipedia)端口、内建计时器支持和用户事件,CPU负荷占用小,资源占用小。
      dispatch sourc联结流程:在任一线程上调用dispatch_source_merge_data 这个函数后,会执行 Dispatch Source 事先定义好的句柄(可以简单理解句柄就是block )(是不是有点通知,回调的味道哈)

      代码展示:

    //全局队列 
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //创建source 
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue()); 
    //定义source的句柄 
    dispatch_source_set_event_handler(source, ^{
      //调用一次dispatch_source_merge_data会调用这个句柄  
      NSLog(@"%lu",dispatch_source_get_data(source)); 
    }); 
    //默认source是suspend的,需要resume生效 dispatch_resume(source); 
    //遍历10次 
    dispatch_apply(10, globalQueue, ^(size_t index) {
      // merge data dispatch_source_merge_data(source, 1); 
    });
    

      这段程序简单逻辑:调用dispatch_source_merge_data 会触发实现定义好的事件

    dispatch_source_set_event_handler(source, ^{ //调用一次dispatch_source_merge_data会调用这个句柄 NSLog(@"%lu",dispatch_source_get_data(source)); });
    dispatch_source_create 函数参数DISPATCH_SOURCE_TYPE_DATA_ADD 累加
    

      当注册系统事件的时候,有时候系统还没来得及通知应用程序,这个时候,系统会累计传递过来的值
    DISPATCH_SOURCE_TYPE_DATA_OR 逻辑或处理累计传递过来的值
    其他:

    DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
    DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
    DISPATCH_SOURCE_TYPE_PROC 监测进程相关事件
    DISPATCH_SOURCE_TYPE_READ 可读取文件映像
    DISPATCH_SOURCE_TYPE_SIGNAL 接收信号
    DISPATCH_SOURCE_TYPE_TIMER 定时器
    DISPATCH_SOURCE_TYPE_VNODE 文件系统变更
    DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像
    
    参考博客:https://www.jianshu.com/p/8920a3da98b8

    相关文章

      网友评论

          本文标题:iOS 多线程(四)GCD

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