GCD与Dispatch Queue


什么是GCD与Dispatch Queue?

GCD:是Grand Central Dispatch缩写,是Apple公司封装的一个用于进行并发程序设计的一个API,它以C语言为基础,将线程的操作抽象为对队列的操作。
  Dispatch Queue:Dispatch Queue是GCD中用到的队列类型,有串行队列、并发队列、主队列等。

GCD有什么用?

在进行iOS开发过程,我们经常需要对多个任务进行操作,这时就需要用到线程,iOS开发中对线程的操作多种多样,有PThreads、NSThread、NSOperation以及GCD。
  Pthread:是类Unix系统中用来对线程进行操作的一组C语言写的API,可以在Unix、Mac OX S、Linux中使用,学习Linux下的C语言多线程会用到这个。
  NSThread:是Apple对Pthread的抽象,NSThread对Pthread的抽象程度较低,在进行多线程开发时,依然需要进行对线程进行创建、使用、销毁等操作。
  NSOperation Queue:Apple基于GCD封装的一种基于Queue操作的并发处理技术。
  GCD:基于C语言封装的一个对任务进行操作的并发处理技术。

  在封装的三种API中,Apple公司最为推崇GCD与NSOperation Queue,因为这两种方式让开发者的注意力转向如何对并发程序进行设计,不用关注线程到底是怎样执行任务的,提高了开发的效率。关于并发程序的设计可以查看官方文档Concurrency and Application Design,里面有对GCD、NSOperation Queue以及其它并发技术的详细讲解。

需要了解的知识

关于GCD,需要串行、并发和同步以及异步这些知识。在GCD中,串行和并发是用来形容Dispatch Queue的。
  并发(concurrent):在操作系统中,同一时间段,有多个程序同时在处理器上执行,就像这些程序在同时进行一样。在单一CPU的机器上,并发技术基于时间片技术,而在多核机器上,并发技术基于多核技术的发展。在GCD中并发队列中的任务可以不用等前一个任务执行完就能执行。
  串行(serial):既然并发是多个程序看起来在同时进行,那么串行就是多个程序排队一个一个执行。在GCD串行队列中,队列中的任务需要等出队的任务执行完后才能接着执行。

在GCD中,同步与异步是指任务之间的制约关系的。
  同步:同步在操作系统中是指进程之间的制约关系。一个进程要等另一个进程执行完成后才能执行,而不是同时执行,是进程之间的执行次序的管理。例如,进程B需要用到进程A的结果才能执行,因此系统会先执行A然后在执行B,进程A与B的关系就是同步。同理,在GCD中,具有同步关系的两个任务,一个需要等另一个执行完后才能执行,某种程度上,同步的效果与串行相似。
  异步:与同步的意思相反,既然同步是一个接一个执行,那么异步就是后一个任务不必等前一个任务执行完,即任务A执行的结果并不影响任务B,任务A与任务B不相关,没有确定的先后顺序。

Dispatch Queue类型

串行队列:任务按被加入到队列中的顺序依次执行,每次只从队中取出一个任务执行。

串行队列

并发队列:同时执行一个或多个任务,但任务仍然是按它们被添加到队列中的顺序开始的。

并发队列

主队列:是一个全局串行队列,它在应用程序的主线程上执行任务。因为它运行在您的应用程序的主线程上,主队列经常被用来作为应用程序的一个关键的同步点。

创建Dispatch Queue

  1. 串行队列

     dispatch_queue_t serial = dispatch_queue_create("serial_queue", 0);
    

参数一是队列名,用于Debug时进行跟踪,参数二是队列属性,创建串行队列,默认为0,或者设为DISPATCH_QUEUE_SERIAL。

  1. 并发队列
    • 自定义并发队列

        dispatch_queue_t concur = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
      

与创建串行队列一样,不过参数二为DISPATCH_QUEUE_CONCURRENT。
* 全局并发队列

        dispatch_queue_t concur = dispatch_get_global_queue(ISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

参数一是全局并发队列的优先级,参数二为未来新特性预留,默认为0即可。全局并发队列分别有四个优先级:

        DISPATCH_QUEUE_PRIORITY_HIGHT,
        DISPATCH_QUEUE_PRIORITY_DEFAULT,
        DISPATCH_QUEUE_PRIORITY_LOW,
        DISPATCH_QUEUE_PRIORITY_BACKGROUND  
  1. 主队列

     dispatch_queue_t main = dispatch_get_main_queue();
    

队列与线程的关系

objc上一篇名为Concurrent Programming: APIs and Challenges的文章中的一张图很好的描述了队列与线程的关系。

队列与线程池

  主队列中的任务都是在主线程中执行的,自定义的串行队列和并发队列中的任务,最终会流入系统的全局队列,系统会从线程池中分配相应的队列去执行。
队列与线程的关系

将任务添加到队列

在GCD中,任务通常是以block的形式添加到队列当中的,任务的添加有两种方式,dispatch_sync(同步)和dispatch_async(异步),这两种添加方式决定了队列中的任务之间的制约关系。

  1. dispatch_sync
      将block提交到队列中,并阻塞当前队列,直到block执行结束才回返回到当前队列,提交的任务之间以同步方式执行。需要注意的是,Apple关于dispatch_sync的API文档中有这样一句话,As an optimization, this function invokes the block on the current thread when possible。作为优化,dispatch_sync阻塞当前队列后,系统会让当前线程执行block语句。

  2. dispatch_async
      不等提交到队列的block开始执行就立即返回到当前线程。该函数将block提交到队列后,系统会从线程池中分配线程依次执行队列中的block(并发队列分配的线程个数由系统的状态决定),这样就实现了异步的执行方式。

代码示例:

dispatch_queue_t serial = dispatch_queue_create("serial_queue", 0);
dispatch_queue_t concur = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(serial, ^{
    NSLog(@"%@-sync",[NSThread currentThread]);
});
dispatch_async(serial, ^{
    NSLog(@"%@-async",[NSThread currentThread])
});
dispatch_main();

以上是对于GCD的基本使用,有关GCD的具体使用可以参考官方文档Dispatch Queue以及官方API参考Dispatch

通过例子我们来看一下,串行队列与并发队列的线程数目。
代码示例:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...

        NSLog(@"Hello, World!");

        dispatch_queue_t serial = dispatch_queue_create("serial_queue", 0);
        dispatch_queue_t concur = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
    
        for(int i = 0; i < 5; i++)
        {
            dispatch_sync(serial, ^{
                NSLog(@"serial-%@-sync",[NSThread currentThread]);
            });
            dispatch_async(serial, ^{
                NSLog(@"serial-%@-async",[NSThread currentThread]);
            });
        }
    
        for(int i = 0; i < 5; i++)
        {
            dispatch_sync(concur, ^{
                NSLog(@"concurrent-%@-sync",[NSThread currentThread]);
            });
            dispatch_async(concur, ^{
                NSLog(@"concurrent-%@-async",[NSThread currentThread]);
            
            });
        }
        dispatch_main();
    }
    return 0;
}

为了结果直观显示,将不测的部分注释后运行,结果如下:

  1. 串行队列同步添加

     2016-08-14 18:20:30.313 GCD[798:89819] Hello, World!
     2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync
     2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync
     2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync
     2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync
     2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync
     Program ended with exit code: 0
    
  2. 串行队列异步添加

     2016-08-14 18:24:36.657 GCD[830:92007] Hello, World!
     2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async
     2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async
     2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async
     2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async
     2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async
    
  3. 并发队列同步添加

     2016-08-14 18:28:53.662 GCD[862:94207] Hello, World!
     2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync
     2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync
     2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync
     2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync
     2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync
    
  4. 并发队列异步添加

     2016-08-14 18:29:39.743 GCD[872:94931] concurrent-<NSThread: 0x100500050>{number = 3, name = (null)}-async
     2016-08-14 18:29:39.743 GCD[872:94932] concurrent-<NSThread: 0x100700560>{number = 4, name = (null)}-async
     2016-08-14 18:29:39.743 GCD[872:94933] concurrent-<NSThread: 0x1007006f0>{number = 5, name = (null)}-async
     2016-08-14 18:29:39.743 GCD[872:94930] concurrent-<NSThread: 0x100403380>{number = 2, name = (null)}-async
     2016-08-14 18:29:39.743 GCD[872:94928] concurrent-<NSThread: 0x100500550>{number = 6, name = (null)}-async
    

对比以上结果,可以得出结论:

  1. 队列的类型决定所需要的线程的个数。串行队列和并发队列执行任务所需要的线程个数不同,串行队列只需要一个线程,并发队列需要多个线程(线程个数由系统决定)。
  2. dispatch_sync和dispatch_async决定了是否开启线程执行队列中的任务。dispatch_sync在当前线程中执行队列中的任务,dispatch_async从线程池中至少获取一个线程来,保证异步执行。
  3. 无论是串行队列,还是并发队列,使用dispatch_sync添加任务后,任务都是在当前线程中执行,这是由于Apple公司优化的结果。
  4. 对于串行队列使用dispatch_async,系统会新开一个线程来执行队列中的任务,并没有开多个线程,可以理解为因为任务一次只执行一个,若每个任务都创建一个线程运行,开销巨大,并不划算,干脆使用同一线程依次执行所有任务。
  5. 对于并发队列使用dispatch_async,由于队列中的任务都需要并发运行,如果只开一个线程,则队列中的任务之间相当于串行执行,所以开了多个线程,至于线程开多少个这由系统决定,例如将for循环的次数改为100,可以看到有多个任务使用同一线程。

参考文章

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

推荐阅读更多精彩内容