iOS多线程之GCD

iOS多线程之GCD

什么是GCD

GCD(grand central dispatch) 是 libdispatch 的市场名称,而 libdispatch 作为 Apple 的一个库,为并发代码在多核硬件(跑 iOS 或 OS X )上执行提供有力支持。它具有以下优点:

  • GCD 能通过推迟昂贵计算任务并在后台运行它们来改善应用的响应性能。
  • GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
  • GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。
相关概念
1. Serial vs. Concurrent 串行 vs. 并发

串行是指同一时间只有一个任务执行,并发是指同一时间可以执行多个任务。

2. Synchronous vs. Asynchronous 同步 vs. 异步

同步是指一个函数等函数体执行完了才返回,异步是指函数立即返回(此时函数体还没有执行完成)。

3. Critical Section 临界区

就是一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码。

4. Race Condition 竞态条件

程序的输出结果依赖于不同事件的执行顺序。

5. Deadlock 死锁

两个(或多个)进程(或线程)都卡住了,等待对方完成或执行某些操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。

6. Thread Safe 线程安全

线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 NSDictionary 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDictionary 就不是线程安全的,应该保证一次只能有一个线程访问它。

7. Context Switch 上下文切换

一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。

8. Concurrency vs Parallelism 并发与并行

并发是指在同一个时间段内可以有多个任务执行,但是同一时刻只能有一个任务执行。并行是指即使在同一时刻也有多个任务执行。

9. Queues 队列

GCD 提供有 dispatch queues 来处理代码块,这些队列管理你提供给 GCD 的任务并用 FIFO 顺序执行这些任务。这就保证了第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。

10. Serial Queues 串行队列

串行队列中的任务一次执行一个,每个任务只在前一个任务完成时才开始。而且,你不知道在一个 Block 结束和下一个开始之间的时间长度,如图



这些任务的执行时机受到 GCD 的控制;唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。
由于在串行队列中不会有两个任务并发运行,因此不会出现同时访问临界区的风险。

11. Concurrent Queues 并发队列

在并发队列中的任务能得到的保证是它们会按照被添加的顺序开始执行,但是无法保证任务的开始必须在上一个任务完成之后,也就是说上一个任务还没完成下一个任务就开始了。任务可能以任意顺序完成,你不会知道何时开始运行下一个任务,或者任意时刻有多少 Block 在运行。如图



何时开始一个 Block 完全取决于 GCD 。如果一个 Block 的执行时间与另一个重叠,也是由 GCD 来决定是否将其运行在另一个不同的核心上,如果那个核心可用,否则就用上下文切换的方式来执行不同的 Block 。

12. Queue Types 队列类型

首先,系统提供给你一个叫做 主队列(main queue) 的特殊队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。这个队列就是用于发生消息给 UIView 或发送通知的。
系统同时提供给你好几个并发队列。它们叫做 全局调度队列(Global Dispatch Queues) 。
最后,你也可以创建自己的串行队列或并发队列。这就是说,至少有五个队列任你处置:主队列、四个全局调度队列,再加上任何你自己创建的队列。

使用
  1. dispatch_queue_create

         //创建一个串行队列
         dispatch_queue_t serialQue = dispatch_queue_create"serialQueue", DISPATCH_QUEUE_SERIAL);
         
         //创建一个并发队列
         dispatch_queue_t conQue = dispatch_queue_create("conCurrentQue", DISPATCH_QUEUE_CONCURRENT);
         
         //创建一个高优先级并发队列
         dispatch_queue_attr_t queAttr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INTERACTIVE, -1);
         dispatch_queue_create(@"attrQue", queAttr);
    
  2. Main Dispatch Que Or Global Dispatch Que
    //获取主队列
    dispatch_queue_t mainQue = dispatch_get_main_queue();

         //获取全局高优先级并发队列
         //优先级
        //  QOS_CLASS_USER_INTERACTIVE > QOS_CLASS_USER_INITIATED > QOS_CLASS_UTILITY > QOS_CLASS_DEFAULT > QOS_CLASS_BACKGROUND
         dispatch_queue_t globalQue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    
  3. dispatch_set_target_queue
    使用dispatch_queue_create创建的queue不管是串行的还是并行的,都使用与默认优先级全局队列相同的优先级。如果要变更生成队列的优先级,就要使用此函数。生成一个高优先级的并发队列的代码入下:
    dispatch_queue_t highQue = dispatch_queue_create("highPri", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t gloablHighQue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    dispatch_queue_t gloablDefaultQue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);

     dispatch_set_target_queue(highQue, gloablHighQue);
             
     dispatch_async(gloablDefaultQue, ^{
             
     NSLog(@"this is defaulPri queue");
     });
     dispatch_async(highQue, ^{
             
     NSLog(@"this is highPri queue");
     });
    

    运行结果如下:

    2016-03-29 09:04:28.958 LZThreadPro04GCD48247:4511919 this is highPri queue
    2016-03-29 09:04:28.958 LZThreadPro04GCD48247:4511909 this is defaulPri queue

  4. dispatch_after
    可以让指定任务延迟执行,如让任务延迟3秒执行。
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            NSLog(@"delayed action");
            
        });
    
  5. Dispatch Group
    在追加到队列中的多个任务全部结束后想执行结束处理。下列代码展示了当几个任务全部结束后才做最后处理的情况。
    NSLog(@"1 + 2 + 3 + ... + 100 = ");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    dispatch_queue_t mainQ = dispatch_get_main_queue();
    __block NSInteger sum1 = 0;
    __block NSInteger sum2 = 0;
    __block NSInteger sum3 = 0;
    __block NSInteger sum4 = 0;
    __block NSInteger sum5 = 0;
    __block NSInteger sum6 = 0;
    __block NSInteger sum7 = 0;
    __block NSInteger sum8 = 0;
    __block NSInteger sum9 = 0;
    __block NSInteger sum10 = 0;
    __block NSInteger sumTotal = 0;

         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 1; j < 11; j ++) {
         sum1 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 11; j < 21; j ++) {
         sum2 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 21; j < 31; j ++) {
         sum3 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 31; j < 41; j ++) {
         sum4 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 41; j < 51; j ++) {
         sum5 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 51; j < 61; j ++) {
         sum6 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 61; j < 71; j ++) {
         sum7 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 71; j < 81; j ++) {
         sum8 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 81; j < 91; j ++) {
         sum9 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 91; j < 101; j ++) {
         sum10 += j;
         }
         
         });
         
         dispatch_group_notify(group, mainQ, ^{
         
         sumTotal = sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7 + sum8 + sum9 + sum10;
         NSLog(@"%li", sumTotal);
         });
    
  6. dispatch_barrier_async
    在访问数据库或文件时,使用串行队列可避免数据竞争的问题。写入处理确实不可与写入处理以及读取处理并行处理,但是如果读取处理与读取处理并行,也不会出问题,为了高效进行访问,读取处理追加到并行队列,写入处理在没有读取处理进行的情况下追加到串行队列。这时候可以使用此函数。
    示例代码如下:
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    for (NSInteger i = 0; i < 10; i ++) {

             dispatch_barrier_async(queue, ^{
             [NSThread sleepForTimeInterval:1];
             NSLog(@"reader... %li", i);
             
             });
             }
             
             dispatch_barrier_async(queue, ^{
             [NSThread sleepForTimeInterval:1];
             NSLog(@"writer...");
             
             });
             
             for (NSInteger i = 10; i < 20; i ++) {
             
             dispatch_barrier_async(queue, ^{
             [NSThread sleepForTimeInterval:1];
             NSLog(@"reader...%li", i);
             
             });
             }
             NSLog(@"---------------");
    
  7. dispatch_suspend dispatch_resume
    当追加大量处理到队列时,在追加处理的过程中,有时希望不执行已追加的处理。这种情况下,只要挂起队列即可,当可以执行再恢复。
    dispatch_suspend(queue);
    dispatch_resume(queue);
    这俩函数对已经执行的处理没有影响,挂起后,追加到队列但尚未运行的处理会停止执行。而恢复则使得这些处理能够继续执行。

  8. dispatch_semaphore
    当并行的处理更新数据时,会产生数据不一样的情况,有时应用程序还会异常结束,此函数可以用来控制线程互斥访问共享资源。如下示例代码
    NSMutableArray *arrayM = [NSMutableArray array];
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);

     for (NSInteger i = 0; i < 1000; i ++) {
     dispatch_async(queue, ^{
        
     dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
     
     [arrayM  addObject:@(i)];
     
     dispatch_semaphore_signal(sem);
     
     });
     }
     }
    
  9. dispatch_once
    该函数是保证在应用程序中只执行一次制定处理的。
    static dispatch_once_t once;

     for (NSInteger i = 0 ; i < 10 ; i ++) {
     dispatch_once(&once, ^{
     
     NSLog(@"print only 1 time");
     });
     }

推荐阅读更多精彩内容

  • GCD (Grand Central Dispatch) :iOS4 开始引入,使用更加方便,程序员只需要将任务添...
    池鹏程阅读 551评论 0 2
  • Demo地址 一 概念 全称是Grand Central Dispatch,中枢调度器 纯C语言,提供了非常多强大...
    Mitchell阅读 541评论 0 4
  • 多线程 在iOS开发中为提高程序的运行效率会将比较耗时的操作放在子线程中执行,iOS系统进程默认启动一个主线程,用...
    郭豪豪阅读 1,607评论 0 4
  • 前言 在学习Android编程的时候,我们经常会使用 runOnUiThread(),把UI相关的操作放到里面。而...
    ChenJZ阅读 11,423评论 22 36
  • 开心大团队特种兵课前作业一:(心态篇) 岁月静好的作业: 一、个人从业经历(300字) 本人十年教育从业经历,从幼...
    马春颖阅读 59评论 0 0