GCD简介

作者: ISwiftUI | 来源:发表于2017-01-21 15:09 被阅读94次

    1.队列

    • 串行队列,串行队列将任务以先进先出(FIFO)的顺序来执行,所以串行队列经常用来做访问某些特定资源的同步处理。你可以也根据需要创建多个队列,而这些队列相对其他队列都是并发执行的。换句话说,如果你创建了4个串行队列,每一个队列在同一时间都只执行一个任务,对这四个任务来说,他们是相互独立且并发执行的。如果需要创建串行队列,一般用dispatch_queue_create这个方法来实现,并指定队列类型DISPATCH_QUEUE_SERIAL。

    • 并发队列,并发队列虽然是能同时执行多个任务,但这些任务仍然是按照先到先执行(FIFO)的顺序来执行的。并发队列会基于系统负载来合适地选择并发执行这些任务。并发队列一般指的就是全局队列(Global queue),进程中存在四个全局队列:高、中(默认)、低、后台四个优先级队列,可以调用dispatch_get_global_queue函数传入优先级来访问队列。当然我们也可以用dispatch_queue_create,并指定队列类型DISPATCH_QUEUE_CONCURRENT,来自己创建一个并发队列。

    • 主队列,与主线程功能相同。实际上,提交至main queue的任务会在主线程中执行。main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。

    2.任务

    • 同步任务,使用dispatch_sync将任务加入队列。将同步任务加入串行队列,会顺序执行,一般不这样做并且在一个任务未结束时调起其它同步任务会死锁。将同步任务加入并行队列,会顺序执行,但是也没什么意义。

    • 异步任务,使用dispatch_async将任务加入队列。将异步任务加入串行队列,会顺序执行,并且不会出现死锁问题。将异步任务加入并行队列,会并行执行多个任务,这也是我们最常用的一种方式。

    3.GCD常见的用法和应用场景

    3.1 dispatch_async(常见的应用场景是异步处理耗时的操作,然后耗时操作处理完毕后,使用主线程更新UI)

    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalQueue, ^{
        // 一个异步的任务,例如网络请求,耗时的文件操作等等
        ...
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI刷新
            ...
        });
    });
    

    3.2 dispatch_after (常用的应用场景是延时调用)

    dispatch_queue_t queue= dispatch_get_main_queue();
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
        // 在queue里面延迟执行的一段代码
        ...
    });
    

    3.3 dispatch_once (常用于单例的创建,只创建一次)

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行一次的任务
        ...
    });
    

    3.4 dispatch_group (GCD组,把一组任务提交到队列中,多个请求完毕后才处理事情,如多个网络请求完毕会,才去更新UI)

    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, ^{
        // 异步任务1
    });
    
    dispatch_group_async(group, queue, ^{
        // 异步任务2
    });
    
    // 等待group中多个异步任务执行完毕,做一些事情,介绍两种方式
    
    // 方式1(不好,会卡住当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    ...
    
    // 方式2(比较好)
    dispatch_group_notify(group, mainQueue, ^{
        // 任务完成后,在主队列中做一些操作
        ...
    });
    

    3.5 dispatch_barrier_async(和dispatch_group类似,dispatch_barrier也是异步任务间的一种同步方式,可以在比如文件的读写操作时使用,保证读操作的准确性。另外,有一点需要注意,dispatch_barrier_sync和dispatch_barrier_async只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样)

    // dispatch_barrier_async的作用可以用一个词概括--承上启下,它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。本例中,任务4会在任务1、2、3都执行完之后执行,而任务5、6会等待任务4执行完后执行。
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 任务1
        ...
    });
    dispatch_async(queue, ^{
        // 任务2
        ...
    });
    dispatch_async(queue, ^{
        // 任务3
        ...
    });
    dispatch_barrier_async(queue, ^{
        // 任务4
        ...
    });
    dispatch_async(queue, ^{
        // 任务5
        ...
    });
    dispatch_async(queue, ^{
        // 任务6
        ...
    });
    

    3.6 dispatch_apply(dispatch_apply有什么用呢,因为dispatch_apply并行的运行机制,效率一般快于for循环的类串行机制(在for一次循环中的处理任务很多时差距比较大)。比如这可以用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性,如果用for循环,耗时较多,并且每个表单的数据没有依赖关系,所以用dispatch_apply比较好)

    // for循环做一些事情,输出0123456789
    for (int i = 0; i < 10; i ++) {
        NSLog(@"%d", i);
    }
    
    // dispatch_apply替换(当且仅当处理顺序对处理结果无影响环境),输出顺序不定,比如1098673452
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    /*! dispatch_apply函数说明
    *
    *  @brief  dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API
    *         该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
    *
    *  @param 10    指定重复次数  指定10次
    *  @param queue 追加对象的Dispatch Queue
    *  @param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
    *
    */
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu", index);
    });
    

    3.7 dispatch_suspend和dispatch_resume(队列的暂停和恢复,已添加到队列中没有执行的任务不会执行,直至等到线程恢复才会继续执行)

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_suspend(queue); //暂停队列queue
    dispatch_resume(queue);  //恢复队列queue
    

    3.8 dispatch_semaphore_signal

    dispatch_semaphore 信号量基于计数器的一种多线程同步机制。在多个线程访问共有资源时候,会因为多线程的特性而引发数据出错的问题。

    // dispatch_semaphore_signal有两类用法:a、解决同步问题;b、解决有限资源访问(资源为1,即互斥)问题。
    // dispatch_semaphore_wait,若semaphore计数为0则等待,大于0则使其减1。
    // dispatch_semaphore_signal使semaphore计数加1。
    
    // a、同步问题:输出肯定为1、2、3。
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
    dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
    dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
    
    dispatch_async(queue, ^{
        // 任务1
        dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
        NSLog(@"1\n");
        dispatch_semaphore_signal(semaphore2);
        dispatch_semaphore_signal(semaphore1);
    });
    
    dispatch_async(queue, ^{
        // 任务2
        dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
        NSLog(@"2\n");
        dispatch_semaphore_signal(semaphore3);
        dispatch_semaphore_signal(semaphore2);
    });
    
    dispatch_async(queue, ^{
        // 任务3
        dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
        NSLog(@"3\n");
        dispatch_semaphore_signal(semaphore3);
    });
    
    // b、有限资源访问问题:for循环看似能创建100个异步任务,实质由于信号限制,最多创建10个异步任务。
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    for (int i = 0; i < 100; i ++) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
        // 任务
        ...
        dispatch_semaphore_signal(semaphore);
        });
    }
    

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    如果semaphore计数大于等于1.计数-1,返回,程序继续运行。
    如果计数为0,则等待。
    这里设置的等待时间是一直等待。

    dispatch_semaphore_signal(semaphore);
    计数+1.
    在这两句代码中间的执行代码,每次只会允许一个线程进入,这样就有效的保证了在多线程环境下,只能有一个线程进入。

    3.9 dispatch_set_context、dispatch_get_context和dispatch_set_finalizer_f(dispatch_set_context可以为队列添加上下文数据,但是因为GCD是C语言接口形式的,所以其context参数类型是“void *”。需使用上述abc三种方式创建context,并且一般结合dispatch_set_finalizer_f使用,回收context内存)

    // dispatch_set_context、dispatch_get_context是为了向队列中传递上下文context服务的。
    // dispatch_set_finalizer_f相当于dispatch_object_t的析构函数。
    // 因为context的数据不是foundation对象,所以arc不会自动回收,一般在dispatch_set_finalizer_f中手动回收,所以一般讲上述三个方法绑定使用。
    
    - (void)test
    {
        // 几种创建context的方式
        // a、用C语言的malloc创建context数据。
        // b、用C++的new创建类对象。
        // c、用Objective-C的对象,但是要用__bridge等关键字转为Core Foundation对象。
    
        dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        if (queue) {
            // "123"即为传入的context
            dispatch_set_context(queue, "123");
            dispatch_set_finalizer_f(queue, &xigou);
        }
        dispatch_async(queue, ^{
            char *string = dispatch_get_context(queue);
            NSLog(@"%s", string);
        });
    }
    
    // 该函数会在dispatch_object_t销毁时调用。
    void xigou(void *context)
    {
        // 释放context的内存(对应上述abc)
    
        // a、CFRelease(context);
        // b、free(context);
        // c、delete context;
    }
    

    4. 常见的死锁

    4.1 dispatch_sync

    // 假设这段代码执行于主队列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // 在主队列添加同步任务
    dispatch_sync(mainQueue, ^{
        // 任务
        ...
    });
    
    // 在串行队列添加同步任务 
    dispatch_sync(serialQueue, ^{
        // 任务
        ...
        dispatch_sync(serialQueue, ^{
            // 任务
            ...
        });
    };
    

    4.2 dispatch_apply

    // 因为dispatch_apply会卡住当前线程,内部的dispatch_apply会等待外部,外部的等待内部,所以死锁。
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, queue, ^(size_t) {
        // 任务
        ...
        dispatch_apply(10, queue, ^(size_t) {
            // 任务
            ...
        });
    });
    

    4.3 dispatch_barrier

    dispatch_barrier_sync在串行队列和全局并行队列里面和dispatch_sync同样的效果,所以需考虑同dispatch_sync一样的死锁问题。

    相关文章

      网友评论

        本文标题:GCD简介

        本文链接:https://www.haomeiwen.com/subject/yktnbttx.html