GCD(Grand_Central_Dispatch) 详解-

什么是线程?####

当一个应用程序安装到 Mac 或 iPhone上的时候, Mac、iPhone 的操作系统 OSX, iOS 会根据用户的姿势启动该应用程序后,一个一个地执行CPU命令行,先执行一个命令,再接着执行另一个命令,这样不断的循环下去。

CPU 一次只能执行一个命令, 不能执行某处分开的并行的两个命令,因此通过 CPU 执行的 CPU 命令就好比一份无分叉的大道,其执行不会出现分歧。所以,1个CPU 执行的CPU命令列为一条无分叉路经即为线程;

但,这种无分叉路经不只一条,存在多条时候,即为多线程,1 个 CPU 核执行多条不同路径上的不同命令。 虽然CPU相关技术很多, 但基本上1个CPU核一次能够执行的CPU命令始终为1。 OS X和 iOS 的内核在发生操作系统事件的时候,会切换执行路经,执行中路经的状态,例如CPU的寄存器等信息保存到各自路经专用的内存中,从切换目标路经专用的内存中复原CPU寄存器等信息,继续执行切换路经的 CPU 命令行,这个被称为`上下文切换`;因而其实线程之间一直进行着上下文切换。

多线程容易出现问题,如 多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待时间的线程会导致多个线程相互持续等待(死锁)、使用太多线程会消耗大量内存等。

死锁
数据竞争.png
大量线程.png

即使有问题,也要采用多线程,这样才能保证应用程序的响应性能,启动后, 最先执行的线程(主线程)描绘界面、处理触摸屏幕的时间等等,若是在主线程中进行大量的数据操作便会阻塞主线程,从而导致没法再更新用户界面,出现长时间卡顿。

主线程卡顿.png

GCD大大简化了偏于复杂的多线程的源代码。

什么是GCD?####

GCD是异步执行任务的技术之一, 一般将应用程序中记述的线程管理用的代码在系统级中的实现,开发者只需要定义想执行的任务并追加到适当的 Dispatch Queue 中,GCD 就能生成必要的线程并计划执行任务,由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样就比以前的线程更有效率。

也就是说,GCD 用我们难以置信的非常简洁的记述方法,实现了极为负责繁琐的多线程编程,可以说这是一项划时代的技术。

dispatch_async(queue, ^{
// 这一行的代码表示处理在后台线程执行
/*
 * 长时间处理的工作
 * 如一些复杂的数据处理
 */
       dispatch_async(dispatch_get_main_queue(),^{
 // 这代码表示在主线程中执行
 /*
  * 只在主线程可以执行的处理
  * 例如用户界面更新
  */    
  });     
 });
Disaptch Queue#####

开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。

就如同:
 
dispatch_async(queue, ^{
 /*
  * 想执行的任务
  */
 })

使用了 Block 语法“定义想执行的任务”,通过 dispatch_async 函数“追加”赋值在变量 queue 的 “Dispatch Queue 中”,这样就可以指定 Block 在另一个线程中执行。“”Dispatch Queue 按照追加的顺序(FIFO)执行处理。

在执行处理时候,存在两种 Disparch Queue, 一种是等待执行中处理的 Serial Dispatch Queue, 另一种是不等待现在执行中处理的 Concurrent Dispatch Queue。

dispatch_async(queue, block0);
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_async(queue, block3);           
dispatch_async(queue, block4);
dispatch_async(queue, block5);

如以上代码,当 queue 为 Serial Dispatch Queue 时候,因为要等待现在执行中的处理结果,所以顺序为

block0
block1
block2
block3
block4
block5

当变量 queue 为 Concurrent Dispatch Queue 时,因为不用等待现在执行中的处理结束,所以执行了 block0 不不管其是否执行结束都执行 block1, 如此重复。

iOS 和 OS X 的核心 --XNU 内核决定应当使用的线程数,并只生成所需要的线程执行处理,当处理结束,应当执行的处理数减少时,XNU 内核会结束不再需要的线程。
dispatch_queue_create#####

通过 Dispatch_queue_create 函数生成 Dispatch Queue。

      dispatch_queue_t mySyrialQueue = dispatch_queue_create("com.queue.serialQueue", DISPATCH_QUEUE_SERIAL);
      dispatch_async(mySyrialQueue, ^{
    NSLog(@"mySyrialQueue block");
      });
      
      // 第一个参数指定 Queue 的名称,建议使用应用程序 ID 这种命名方式, 当然也可以设置为 NULL;
      // 第二个参数指定  DISPATCH_QUEUE_SERIAL 或 NULL 表示 生成 Serial Dispatch Queue 

虽然 Serial Dispatch Queue 同时只能追加一个处理,但当生成多个 Serial Dispatch Queue 时,各个 Serial Dispatch Queue 将并行执行,即同时执行多个。系统对于一个 Serial Dispatch Queue 就只能生成并使用一个线程, 如果生成2000个 Serial Dispatch Queue, 那么就可以生成2000多个线程。如果过多的使用线程,就会消耗大量的内存,引起大量的上下文切换,大幅度降低系统的响应性能。

所以, 尽量在“避免多个线程更新相同资源导致数据竞争时”使用 Serial Dispatch Queue

使用 Serial Dispatch Queue
    dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.queue.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(myConcurrentQueue, ^{
    NSLog(@"myConcurrentQueue block");
    });
    // 第一个参数指定 Queue 的名称,建议使用应用程序 ID 这种命名方式, 当然也可以设置为 NULL;
    // 第二个参数指定  DISPATCH_QUEUE_CONCURRENT 生成 Concurrent Dispatch Queue 
Main Dispatch Queue/Global Dispatch Queue

除了创建线程之外, 系统还提供了标准的 Dispatch Queue, 就是 Main Dispatch Queue 和 Global Dispatch Queue。

  • Main Dispatch Queue
    主线程, 是一个 Serial Dispatch Queue,追加到主线程的处理在主线程的 RunLoop 中执行, 由于在主线程中执行,因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到 Main Dispatch Queue 使用。

  • Global Dispatch Queue
    Global Dispatch Queue 是所有的应用程序都能使用的 Concurrent Dispatch Queue,没有必要通过 dispatch_queue_create 函数来逐个生成 Concurrent Dispatch Queue。只要获取即可。它包含四个执行优先级。

    • 高优先级(High Priority)
    • 默认优先级(Default Priority)
    • 低优先级(Low priority)
    • 后台优先级(Background Poriority)

    在向 Global Dispatch Queue 追加处理的时候,应选择与处理内容对应的执行优先级的 Global Dispatch Queue。但是并不能保证实时性,因此优先级只是大致的判断。

     // Main Dispatch Queue 的获取方法
     dispatch_queue_t mainDisatchQueue = dispatch_get_main_queue();

     // Global Dispatch Queue(高优先级)的获取方法
 
     dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
     DISPATCH_QUEUE_PRIORITY_HIGH        // 高优先级
     DISPATCH_QUEUE_PRIORITY_LOW         // 低优先级
     DISPATCH_QUEUE_PRIORITY_DEFAULT     // 默认优先级
     DISPATCH_QUEUE_PRIORITY_BACKGROUND  // 后台优先级
      
     // 使用 Main Dispatch Queue 和 Global Dispatch Queue 源码
     /*
      * 在默认的优先级的 Global Dispatch Queue 中执行 block
      */
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
      //  可并行执行的处理
      //  在 main 中block
      dispatch_async(dispatch_get_main_queue(), ^{
        
        // 在 主线程中 执行 Block
        });
       
     });
dispatch_after#####
  • 在指定时间后执行处理的情况,可以使用 dispatch_after, 需要注意的是 dispatch_after 并不是在指定时间后执行处理,而是在指定时间追加处理到 Dispatch Queue。
  dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_MSEC);
  
    // ull为C语言数值字面量。
    
  dispatch_after(time, dispatch_get_main_queue(), ^{
 
   NSLog(@"waited at least three secons");
  });
   // 第一个参数 指定时间用的 dispatch_time_t 类型的值 使用 dispatch_time 函数或者 dispatch_walltime 函数构成
   // dispatch_time_t 表示相对时间, dispatch_walltime 表示绝对时间
  
 以上代码,因为 Main Dispatch Queue 在主线程的 RunLoop 中执行,所以必入每隔 1/60 秒执行的 RunLoop 中, Block 最快在3秒后执行,最慢也在3+1/60秒后执行,
  • dispatch_time_t 函数
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_MSEC);
    
 函数获取从第一个参数 制定的时间开始到第二个参数指定的毫微秒单位时间后的时间。第一个参数经常使用的值是 `DISPATCH_TIME_NOW` 表示现在的时间。 "ull"表示 C 语言的数字面量(表示“unsigned long long”)
      
    NSEC_PER_MSEC 表示以毫秒为单位
    NSEC_PER_SEC  表示为秒
Dispatch Group#####

在追加到 Dispatch Queue 中的多个处理全部结束后限制性结束处理, 这种情况经常出现, 当为 Serial Dispatch Queue 时候, 只要将想要执行的处理全部追加到 Serial Dispatch Queue 中并在最后追加结束处理即可实现,但是要使用 Concurrent Dispatch Queue 时或同事使用多个 Dispatch Queue 时候,源码就会变得颇为复杂。
此种情况适合使用 Dispatch Group。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{NSLog(@"block1");});
dispatch_group_async(group, queue, ^{NSLog(@"block2");});
dispatch_group_async(group, queue, ^{NSLog(@"block3");});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"done");
});

// 执行结果
block1
block2
block3
done

因为向 Global Dispatch Queue 即 Concurrent Dispatch Queue 追加处理,多个线程并行处理执行,所以追加处理的执行顺序不定,执行时候会发生变化,但是此执行结果的 done 一定是最后输出的。

无论向什么样的 Dispatch Queue 中追加处理, 使用 Dispatch Group 都可监视这些处理执行的结束,一旦监测到所有的处理执行结束, 就可以结束的处理追加到 Dispatch Queue 中, 这就是使用 Dispatch Group 的原因。

dispatch_group_notify 函数第一个参数为指定的要监视的 Dispatch Group. 在追加到该 Dispatch Group 的全部处理执行结束后,将第三个参数的 block 追加第二个参数的 Dispatch Queue 中, 在 dispatch_group_notify 函数中不管指定什么样的 Dispatch Queue。属于 Dispatch Group 的全部处理在追加到指定的 Block 时都已执行结束。

除了 dispatch_group_async 之外,还可以使用dispatch_group_wait 仅等待全部处理执行结束。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{NSLog(@"block1");});
dispatch_group_async(group, queue, ^{NSLog(@"block2");});
dispatch_group_async(group, queue, ^{NSLog(@"block3");});

NSInteger i = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 如果 i = 0 表示处理结束, 反之 Group 并没执行完。

 第二个参数,为 dispatch_time_t 类型,为等待的时间, 假设block1里执行了复杂的耗时操作超过一秒, 当第二个参数为小于1 秒时, 这 `dispatch_group_wait` 返回的 肯定不为0 则表示为 Group 并没执行完, 假设第二个参数为 `DISPATCH_TIME_FOREVER` ,意味永久等待,所以只要 Group 的处理尚未执行结束, 就会一直等待, 中途不能取消。 也就 `dispatch_group_wait` 返回的值也一定为0;
dispatch_barrier_async#####

访问数据库或者文件的时候,如前所述,使用 Serial Dispatch Queue 可避免数据竞争的问题。 写入数据处理不可与其他写入数据处理以及包涵读取处理的其它某些处理并行执行,但是如果读取处理只是与读取处理并行执行, 那么多个并行执行就不会发生问题。
首先,用 dispatch_queue_create 函数生成 Concurrent Dispatch Queue, 在 dispatch_async 中追加读取处理。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.forBarrier", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, block0_for_reading);
    dispatch_async(queue, block1_for_reading);
    // 进行写入操作
    dispatch_async(queue, block0_for_writing);
    //
    dispatch_async(queue, block2_for_reading);
    dispatch_async(queue, block3_for_reading);

若按此代码执行, 在 bock1 与 block2 之间执行写入操作,就可能会在追加写入之前的处理中读取到与期待不符合的数据,还可能因为非法的访问导致应用程序一场。若采用以下操作:

       dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.forBarrier", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, block0_for_reading);
    dispatch_async(queue, block1_for_reading);
    // 进行写入操作
    dispatch_barrier_async(queue, block0_for_writing);
    //
    dispatch_async(queue, block2_for_reading);
    dispatch_async(queue, block3_for_reading);
dispatch_barrier_async函数.png

则会有效的防止出现的问题,因为 dispatch_barrier_async 函数会等待追加到 Concurrent Dispatch Queue 上的并执行的处理全部结束之后,再将指定的处理追加到该 Concurrent Dispatch Queue 中,然后由 dispatch_barrier_async 函数追加的处理执行完毕之后,Concurrent Dispatch Queue 的处理又开始执行。

以上内容来整理来自于 (Objective-C高级编程 iOS与OS X 多线程和内存管理)

gcd🌰戳这

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容