前言

iOS与OS X多线程和内存管理 读书笔记(三) GCD

笔记

GCD概要

  1. GCD:Grand Central Dispatch,大中枢派发,很牛逼的名字

GCD的API

  1. dispatch_async(queue, block);

    将想要执行的任务封装进Block,放他到queue中执行。这里的queue有两种:

    1. 一种是Serial Dispatch Queue,顺序队列,放入这里面的block任务是一个一个执行的,先进先出。
    2. 另一种是 Concurrent Dispatch Queue,并行队列,意思就是会开多个队列,block一次进入各个队列同时执行;但是队列的数量由系统控制,那些还来不及进入任何一个队列执行的block,只能等某个队列的block执行完了,再进入队列执行了;所以说这里的每个block的执行顺序是不确定的。

    PS:当然你也可以同时开多个顺序队列,以达到并行队列的效果;但是不能多开,因为程序在各个队列之间切换,也就是在各个线程之间切换,也就是所谓的上下文切换,需要耗费CPU等资源。当然存放每个线程的状态等信息也需要耗费资源。

  2. dispatch_queue_create;

    这个API用来生成Dispatch Queue。看看下面代码:

    1
    dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.vanney9.serialQueue", NULL);

    上面的代码用来生成一个Serial dispatch queue,这个函数有两个参数:第一个是queue的名字,最好唯一;第二个是代表生成的queue的类型,若指定为NULL,则生成serial dispatch queue,若指定为DISPATCH_QUEUE_CONCURRENT,则生成concurrent dispatch queue。返回类型是dispatch_queue_t

    PS:生成的queue,要通过dispatch_release函数手动释放。

  3. dispatch_get_main_queue();

    这个函数用来获取程序的主线程,主线程只有一个,当然是serial dispatch queue。追加到主线程执行的任务会在主线程的Runloop中执行。

  4. dispatch_get_global_queue(level, 0);

    这个函数用来获取Global dispatch queue。它是一个Concurrent dispatch queue。它有4个人优先级:High Priority,Default Priority,Low Priority和Background Priority。

  5. dispatch_set_target_queue (有点晕)

    该函数用来设置queue的优先级。默认情况下,使用dispatch_queue_create生成的queue都是和Global dispatch queue的默认优先级相同。可以使用该函数来改变优先级:

    1
    2
    3
    dispatch_queue_t mySerialQueue = dispatch_queue_create("com.vanney9.serialQueue", NULL); // 优先级为Default Priority
    dispatch_queue_t globalBackgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); // 获取Background Priority的queue
    dispatch_set_target_queue(mySerialQueue, globalBackgroundQueue); // 设置mySerialQueue的优先级和globalBackgroundQueue的相同
  6. dispatch_after(time, queue, block);

    该函数表示在time时间之后(比如3s后)将block加到queue中。

  7. Dispatch Group

    追加到dispatch queue中的多个处理都结束时,想进行一些处理该如何实现呢?对于serial dispatch queue,这很简单;但是如果是concurrent dispatch queue,这时候就需要Dispatch Group。如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //先创建一个concurrent queue
    dispatch_group_t group = dispatch_group_create(); // 创建一个group

    /* 再将3个Block添加到这个queue中,并且属于同一个group */
    dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
    dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
    dispatch_group_async(group, queue, ^{NSLog(@"blk3");});

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"Done");}); // 全部结束后通知主线程,将Block追加到主线程中

    dispatch_release(group); // 释放group。因为Global dispatch queue不需要我们手动释放

    上面的dispatch_group_notify函数在group中的三个block全部都执行完之后,才会将^{NSLog(@"Done");}这一个Block追加到主线程中去。还可以使用dispatch_group_wait函数,如下:

    1
    2
    3
    4
    5
    6
    7
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull *NSEC_PER_SEC); // 1s的时间
    long result = dispatch_group_wait(group, time); // 表示group经过1s之后,是什么结果
    if (result == 0) {
    /* result == 0,那么group中的Block已经全部处理完 */
    } else {
    /* result != 0, 那么还没处理完 */
    }

    PS:注意,执行dispatch_gropu_wait的线程是处于停滞状态的,只有当该函数返回后,才会执行之后的操作,所以还是推荐使用dispatch_group_notify;,异步,无阻塞。

  8. dispatch_barrier_async

    该函数和concurrent dispatch queue一起使用,看看例子

    1
    2
    3
    4
    5
    6
    7
    8
    dispatch_async(queue, blk_reading1); // 注意这里queue是concurrent dispatch queue 
    dispatch_async(queue, blk_reading2);
    dispatch_async(queue, blk_reading3);
    dispatch_barrier_async(queue, blk_writing);
    dispatch_async(queue, blk_reading4);
    dispatch_async(queue, blk_reading5);
    dispatch_async(queue, blk_reading6);
    dispatch_async(queue, blk_reading7);

    上面的代码会同时先执行blk_reading1,blk_reading2和blk_reading3这三个读取操作,然后等执行完后再单独执行blk_writing这一写入操作,最后等写入完成后再开始同时执行之后的4的读取操作。

  9. dispatch_sync

    该函数同步执行block,也就是说block不执行完,就一直停留在这个函数这里,容易导致死锁。看看例子:

    1
    2
    dispatch_queue_t queue = dispatch_get_main_queue(); // 注意,这段代码在主线程上执行。获取主线程
    dispatch_sync(queue, block); // 死锁

    dispatch_sync在主线程上将block追加到主线程去处理,这时候程序一直卡在dispatch_sync这里。因为该函数一直在等待block处理结束;但是block的处理也是在主线程上,需要等当前这些操作结束后,才会去执行block。这样就卡住了,死锁,GG。

  10. dispatch_apply

该函数同样要等待block执行完,才结束。它可以指定block的执行次数。

  1. dispatch_suspend(queue)/dispatch_resume(queue)

    1. dispatch_suspend(queue);:挂起queue,未执行的block暂停
    2. dispatch_resume(queue);:恢复queue,继续执行之前没有执行的block
  2. 一些其他API

    1. Dispatch Semaphore:更细粒度的多线程控制,类似于信号量
    2. dispatch_once:整个应用程序中只执行一次,多线程安全,单例模式
    3. Dispatch I/O:文件分块读取,提高文件读取效率