当前位置 博文首页 > Devhl:GCD之队列的实现和使用

    Devhl:GCD之队列的实现和使用

    作者:Devhl 时间:2021-02-03 22:28

    一、什么是GCD?

    以下是摘自苹果的官方说明。

    Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。

     

    二、GCD实现之Dispatch Queue

    • 用于管理追加的Block的C语言层实现的FIFO队列
    • Atomic函数中实现的用于排他控制的轻量级信号
    • 用于管理线程的C语言层实现的一些容器

    GCD的API全部包含在libdispatch库中的C语言函数。Dispatch Queue通过结构体和链表,被实现为FIFO队列。FIFO队列管理是通过dispatch_async等函数所追加的Block。

    Block并不是直接加入FIFO队列,而是先加入Dispatch Continuation这一dispatch_continution_t类结构体中,然后再加入FIFO队列。该Dispatch Continuation用于记忆Block所属的Dispatch Group和其他一些信息,相当于一般常说的执行上下文。

    Dispatch Queue可通过dispatch_set_target_queue函数设定,可以设定执行该Dispatch Queue处理的Dispatch Queue为目标。该目标可像串珠子一样,设定多个连接在一起的Dispatch Queue。但是在连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于Serial Dispatch Queue的各种优先级的 Global Dispatch Queue.

    Global Dispatch Queue有如下8种。

    • Global Dispatch Queue (High Priority)
    • Global Dispatch Queue (Default Priority)
    • Global Dispatch Queue (Low Priority)
    • Global Dispatch Queue (Background Priority)
    • Global Dispatch Queue (High Overcommit Priority)
    • Global Dispatch Queue (Default Overcommit Priority)
    • Global Dispatch Queue (Low Overcommit Priority)
    • Global Dispatch Queue (Background Overcommit Priority)

    优先级中附有Overcommit的Global Dispatch Queue使用在Serial Dispatch Queue中。如Overcommit这个名称所示,不管系统状态如何,都会强制生成线程的Dispatch Queue。

    这8种Global Dispatch Queue各使用1个pthread_workqueue。GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_workqueue。 

    pthread_workqueue包含在Libc提供的pthreads API中。其使用bsdthread_register和workq_open系统调用,在初始化XNU内核的workqueue之后获取workqueue信息。

    XNU内核持有4中workqueue。

    • WORKQUEUE_HIGH_PRIOQUEUE
    • WORKQUEUE_DEFAULT_PRIOQUEUE
    • WORKQUEUE_LOW_PRIOQUEUE
    • WORKQUEUE_BG_PRIOQUEUE

    以上为4种执行优先级的workqueue。该执行优先级与Global Dispatch Queue的4种执行优先级相同。

     

    Dispatch Queue中执行Block的过程。

    当在Global Dispatch Queue中执行Block时,libdispatch从Global Dispatch Queue自身的FIFO队列中取出Dispatch Continuation,调用pthread_workqueue_additem_np函数。将该Global Dispatch Queue自身、符合其优先级的workqueue信息以及为执行Dispatch Continuation的回调函数等传递给参数。

      

    pthread_workqueue_additem_np函数使用workq_kernreturn系统调用,通知workqueue增加应当执行的项目。根据该通知,XNU内核基于系统状态判断是否需要生成线程。如果是Overcommit优先级的Global Dispatch Queue,workqueue则始终生成线程。

    workqueue的线程执行pthread_workqueue函数,该函数调用libdispatch的回调函数。在该回调函数中执行加入到Dispatch Continuation的Block。

    Block执行结束后,进行通知Dispatch Group结束、释放Dispatch Continuation等处理,开始准备执行加入到Global Dispatch Queue中的下一个Block。

    以上就是Dispatch Queue执行的大概过程。

     

    三、线程和队列

    线程是代码执行的路径,队列则是用于保存以及管理任务的,线程负责去队列中取任务进行执行。

    1、队列

    是管理线程的,相当于线程池,能管理线程什么时候执行。

    队列分为串行队列和并行队列等

    串行队列:队列中的任务按顺序执行

    并行队列:队列中的任务会并发执行。任务执行完毕了,不一定出队列。只有前面的任务执行完了,才会出队列。

     

    串行队列:队列中的任务只会顺序执行,多个串行队列可并行执行

    dispatch_queue_t q = dispatch_queue_create(“xxx”,DISPATCH_QUEUE_SERIAL);

     

    并行队列:队列中的任务会并发执行

    dispatch_queue_t q = dispatch_queue_create(“xxx”, DISPATCH_QUEUE_CONCURRENT);

     

    全局队列:与并行队列类似,但调试时,无法确认操作所在队列

    dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);

     

    主队列:每一个程序对应唯一一个主队列;在多线程开发中,使用主队列更新UI

    dispatch_queue_t q = dispatch_get_main_queue();

     

    2、同步和异步

    dispatch_async (异步操作函数),就是将指定的Block“非同步”地追加到指定的队列(queue)中。dispatch_async函数不做任何等待。会新开线程

     

     

     

     

    dispatch_sync( 同步操作函数),就是将指定的Block“同步”地追加到指定的队列(queue)中。在追加Block结束之前,dispatch_sync函数会一直等待;不会新开线程

     

     

       

    3、队列和操作的组合

    串行队列同步操作:同步操作不会新开线程、操作顺序执行

    串行队列异步操作:异步操作新开一个子线程、操作顺序执行,“最安全的选择”

    并行队列同步操作:同步操作不会新开线程、操作顺序执行

    并行队列异步操作:异步操作会新开多个线程(有多少任务,就开n个线程执行)、操作无序执行;队列前如果有其他任务,会等待前面的任务完成之后再执行;场景:既不影响主线程,又不需要顺序执行的操作! 

    全局队列异步操作:异步操作会新建多个线程、操作无序执行,队列前如果有其他任务,会等待前面的任务完成之后再执行

    全局队列同步操作:同步操作不会新建线程、操作顺序执行

    主队列异步操作:异步操作都在主线程上顺序执行的,不存在异步的概念

    主队列同步操作:会死锁

     

    4、会引起死锁的2种情况

    1、在主线程中运用主队列同步。

    - (void)viewDidLoad {

        [super viewDidLoad];

        dispatch_sync(dispatch_get_main_queue(), ^{      

            NSLog(@"hello");

        });

    }

    同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务,viewDidLoad才会继续向下执行。

    而viewDidLoad和任务都是在主队列上的,由于队列的先进先出原则,任务又需等待viewDidLoad执行完毕后才能继续执行,viewDidLoad和这个任务就形成了相互循环等待,就造成了死锁。

    想避免这种死锁,可以将同步改成异步dispatch_async,或者将dispatch_get_main_queue换成其他串行或并行队列,都可以解决。

     

     2、在串行队列中同步的向这个串行队列追加Block

     dispatch_queue_t serialQueue = dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);

     dispatch_async(serialQueue, ^{

            dispatch_sync(serialQueue, ^{

                NSLog(@"hello");

            });

    });

    想避免这种死锁,可以将同步改成异步dispatch_async,或者将串行队列换为并行队列,都可以解决。

     

    以上部分内容参考自《Objective-C高级编程》一书

     

    bk
    下一篇:没有了