OC之GCD

Dispatch : Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system.

1、GCD

阅读本文之前,需要首先了解一些关于并发编程的概念,使用并发编程的优点、困难等!
需要示例的可以去下载示例demo。

1.1、什么是 GCD?

GCD 是 Grand Central Dispath 的缩写,它是基于 C 语言的API,支持分派队列执行任务的功能。使用GCD分派队列(dispath queue)可以同步或者异步地执行代码,以及串行或者并发的执行任务。与操作队列一样,分派队列也比线程更易于使用,执行异步或者并发任务更高效。
我们日常开发所使用的GCD的API全部为包含在libdispatch库中的C语言函数。
有兴趣的同学也可以去阅读苹果官方文档。也可以下载阅读libdispatch源码!

1.2、为什么使用 GCD?

应用程序中编写的线程管理用的代码要在系统级实现。

无论我们如何努力编写管理线程的代码,在性能方面也不可能胜过系统级所实现GCD。GCD是iOS 和 OS X 的核心 XNU 内核所实现的。因此,使用GCD比使用pthreadsNSThread这些一般的多线程编程API要好。

1.3、GCD 内存管理

当使用Objective-C构建应用程序时,GCD对象被称为dispatch object,所有的dispatch objects 都是 Objective-C 对象。dispatch object像Cocoa对象一样是引用计数的。因此,如果我们使用ARC,dispatch objects将被保留并自动释放,就像任何其他Objective-C对象一样。当使用MRC时,需要使用dispatch_retaindispatch_release函数来保留和释放分派对象,不能使用Core Foundation框架的 retain/release函数。

思考:在GCD中是dispatch_object是什么?如何将C语言的数据类型转为dispatch_object?在ARC下,系统如何实现了dispatch_retaindispatch_release函数的功能?

1.4、dispatch_object

为了后文讲解,我们不妨先去GCD源码看下dispatch_object的内部实现:

typedef union {
    struct dispatch_object_s *_do;
    struct dispatch_continuation_s *_dc;
    struct dispatch_queue_s *_dq;
    struct dispatch_queue_attr_s *_dqa;
    struct dispatch_group_s *_dg;
    struct dispatch_source_s *_ds;
    struct dispatch_source_attr_s *_dsa;
    struct dispatch_semaphore_s *_dsema;
    struct dispatch_data_s *_ddata;
    struct dispatch_io_s *_dchannel;
    struct dispatch_operation_s *_doperation;
    struct dispatch_disk_s *_ddisk;
} dispatch_object_t __attribute__((transparent_union));

可以看到dispatch_object_t的声明:使用关键字union表明了dispatch_object_t是一个“联合”类型的数据结构;我们可以看到dispatch_object_t可以表示的GCD对象;

“联合”是一种构造类型的数据结构。在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的。

2、分派队列Dispatch Queues

GCD 提供并管理FIFO队列,应用程序可以将任务以块对象的形式提交给这些队列,提交给分派队列的任务在线程池(由系统完全管理的)中执行。

2.1、什么是队列?

队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
dispatch_queue按FIFO顺序调用提交给它的dispatch_block;
队列是执行任务的等待队列,即用来存放任务的队列。它衍生出来两个分类:并发和串行队列,一条线程上可以有多个队列。

  • 串行队列(Serial Dispatch Queue):每次只有一个任务被执行,一个任务执行完毕后,再执行下一个任务(只开启一个线程);
    为了避免多线程下的资源竞争问题,一般使用串行队列,同一时间这个队列只有一个线程来访问这个共享资源;
  • 并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行(可以开启多个线程,并且同时执行任务)
    并发队列 + 同步执行: 同步执行只能在当前线程中执行任务,不具备开启新线程的能力。所以 并发队列 + 同步执行 只会在一条线程里面同步执行这些任务,又由于同步执行在当前线程中执行,这个时候就需要一个任务执行完毕后,再执行下一个任务。
    并发队列 + 异步执行:对应着开启异步线程执行要执行的任务,就会同一时间又许多的任务被执行。
    注意:并发队列 的并发功能只有在异步(dispatch_async())函数下才有效

两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

2.2、GCD中的dispatch_queue到底是什么呢?

我们已经大致了解了什么是队列!那么在GCD中的dispatch_queue到底是什么呢?我们不妨去GCD源码里看一下:

typedef struct dispatch_queue_s *dispatch_queue_t;

可以看到dispatch_queue_t的声明:使用关键字typedef为结构体struct dispatch_queue_s定义了一个新的别名*dispatch_queue_t;也就是定义了一个 dispatch_queue_t 类型的指针,指向一个 dispatch_queue_s 类型的结构体。
那我们接着去看看结构体struct dispatch_queue_s的内部成员:

 #define DISPATCH_STRUCT_HEADER(x, y) \
     const struct y *do_vtable; \
     struct x *volatile do_next; \
     unsigned int do_ref_cnt; \
     unsigned int do_xref_cnt; \
     unsigned int do_suspend_cnt; \
     struct dispatch_queue_s *do_targetq; \
     void *do_ctxt; \
     void *do_finalizer;

#define DISPATCH_QUEUE_HEADER \
    uint32_t volatile dq_running; \
    uint32_t dq_width; \
    struct dispatch_object_s *volatile dq_items_tail; \
    struct dispatch_object_s *volatile dq_items_head; \
    unsigned long dq_serialnum; \
    dispatch_queue_t dq_specific_q;
 
 
 struct dispatch_queue_s {
     DISPATCH_STRUCT_HEADER(dispatch_queue_s, dispatch_queue_vtable_s);
     DISPATCH_QUEUE_HEADER;
     char dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE]; // must be last
     char _dq_pad[DISPATCH_QUEUE_CACHELINE_PAD]; // for static queues only
 };
2.3、main_queueglobal_queue

GCD 中有两个特殊的队列:主队列 与 全局并发队列

//获取主队列(与主线程关联的串行队列)
dispatch_queue_t dispatch_get_main_queue(void)
{
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}

//声明主队列 _dispatch_main_q
struct dispatch_queue_s _dispatch_main_q;

//主队列:结构体成员的赋值
struct dispatch_queue_s _dispatch_main_q = {
#if !DISPATCH_USE_RESOLVERS
    .do_vtable = &_dispatch_queue_vtable,
    .do_targetq = &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],
#endif
    .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
    .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
    .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
    .dq_label = "com.apple.main-thread",
    .dq_running = 1,
    .dq_width = 1,
    .dq_serialnum = 1,
};



/*获取全局并发队列
long identifier :使用此队列执行的任务的优先级
unsigned long flags :为将来使用而保留的标志,这个参数指定0
*/
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
  • 主队列main_queue:主队列由系统自动创建,并与应用程序的主线程相关联
  • 全局并发队列global_queue:是由系统定义的全局并发队列。

全局并发队列global_queue有四个优先级,通过XNU内核管理的global_queue的线程,将各自使用的global_queue的执行优先级作为线程的执行优先级使用。在向global_queue追加任务时,应选择与任务内容对应的执行优先级的global_queue。但是通过XNU内核用于global_queue的线程并不能保证实时性,因此执行优先级只是大致的判断。

队列优先级 值描述
DISPATCH_QUEUE_PRIORITY_HIGH 最高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台
2.4、如何创建一个分派队列?

我们可以使用下述函数创建一个分派队列

//dispatch_queue 是一个轻量级对象,应用程序将块提交给该对象以供后续执行
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
        dispatch_queue_attr_t _Nullable attr);

函数有两个参数:

  • const char *_Nullable label附加到队列的字符串标签,用于在调试工具(如工具、示例、堆栈快照和崩溃报告)中唯一地标识队列。推荐使用反向dns命名样式如 "com.demo.gcd.taskA"。这个参数是可选的,可以为空。
  • 第二个参数指定创建的队列类型:如串行队列DISPATCH_QUEUE_SERIAL(按FIFO顺序执行)、并发队列DISPATCH_QUEUE_CONCURRENT
队列类型 值描述
DISPATCH_QUEUE_SERIAL 串行队列,队列中的任务按先进先出的顺序连续执行
DISPATCH_QUEUE_SERIAL_INACTIVE 串行队列,此时这个串行队列的状态是不活跃的,在这个串行队列调用之前,必须使用dispatch_activate()函数激活队列(仅支持ios 10.0及以后的系统)
DISPATCH_QUEUE_CONCURRENT 并发队列,同时执行任务块。虽然它们同时执行块,但可以使用barrier块在队列中创建同步点
DISPATCH_QUEUE_CONCURRENT_INACTIVE 并发队列,此时这个并发队列的状态是不活跃的,在这个并发队列调用之前,必须使用dispatch_activate()函数激活队列(仅支持ios 10.0及以后的系统)

我们可以使用下述函数获取一个分派队里的字符串标签:

const char * dispatch_queue_get_label(dispatch_queue_t _Nullable queue);

如下面代码:

dispatch_queue_t testQueue = dispatch_queue_create("com.demo.gcd.taskA", DISPATCH_QUEUE_CONCURRENT);
const char *lable = dispatch_queue_get_label(testQueue);
NSLog(@"lable = %s",lable);
//输出:com.demo.gcd.taskA

思考:我们知道全局并发队列global_queue有执行的优先级,那么通过dispatch_queue_create()函数生成的队列肯定也有优先级。那么通过dispatch_queue_create()函数生成的队列的优先级是什么?串行队列与并发队列的优先级是否相同?我们能否获取创建队列的优先级?我们能否改变队列的优先级?

2.5、队列的性能问题

不论使用dispatch_queue_create()函数生成的并发队列或者使用dispatch_get_global_queue()获取的并发队列,在使用dispatch_async()函数执行任务时,都可以并发处理多个任务,但并发执行的任务数量取决于当前系统的状态。iOS 或者 OS X 基于dispatch_queue中的任务数量、CPU核数以及CPU负荷等当前系统的状态来决定并发队列中并发执行的任务数量。iOS 和 OS X 的核心XNU 内核决定应当使用的线程数,并且只生成所需的线程执行处理。当任务执行结束,应当执行的任务减少时,XNU 内核会结束不再需要的线程。XNU 内核仅适用并发队列便可完美的管理并执行多个处理任务的线程。

我们已经知道,一个串行队列需要开辟一条线程来处理队里中的任务,那么也就是说假如在短时间内大量生成多个串行队列,那么就需要系统生产多个线程去处理对应的串行队列中的任务。而此时就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能。而对于并发队列来说,不管生成多少个并发队列,由于 XNU 内核只使用有效管理的线程,因此不会发生串行队列的那些问题。
我们分别测试下大量串行队列处理任务 与 大量并发队列处理任务的性能消耗:

串行队列的性能消耗
- (void)dispatch_serial_morequeue_Method
{
    dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
        dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            NSLog(@"开始执行task%zu === %@",index,NSThread.currentThread);
            [NSThread sleepForTimeInterval:13];//模拟耗时任务
            NSLog(@"结束执行task%zu=== %@",index,NSThread.currentThread);
        });
    });
}

我们利用dispatch_apply()函数并发迭代了1000次,创建了 1000 条串行队列,每条串行队列都要执行一个耗时任务。理论上来说,系统需要创建 1000 条线程来处理这 1000 个串行队列中的任务,但是实际又如何呢?
我们运行程序,观看打印结果:

11:05:46 开始执行task511 === {number = 511, name = (null)}
11:05:46 开始执行task483 === {number = 512, name = (null)}
11:05:46 开始执行task512 === {number = 513, name = (null)}
11:05:46 开始执行task507 === {number = 514, name = (null)}

注意,此处打印时抽取的线程数number最大处摘选的几条打印结果:
从打印结果可以看到:XNU 内核生成了 500 多条线程来处理 1000个耗时任务!这500多条处理任务的线程,对于系统是一个不小的负担。

并发队列的性能消耗
- (void)dispatch_concurrent_morequeue_Method
{
    dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
        dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            NSLog(@"开始执行task%zu === %@",index,NSThread.currentThread);
            [NSThread sleepForTimeInterval:13];//模拟耗时任务
            NSLog(@"结束执行task%zu=== %@",index,NSThread.currentThread);
        });
    });
}

我们利用dispatch_apply()函数并发迭代了1000次,创建了 1000 条并发队列,每条并发队列都要执行一个耗时任务。XNU 内核使用有效管理的线程来处理这 1000 个并发队列中的任务,我们运行程序,观看打印结果:

11:08:42 开始执行task60 === {number = 63, name = (null)}
11:08:42 开始执行task61 === {number = 62, name = (null)}
11:08:42 开始执行task62 === {number = 64, name = (null)}
11:08:42 开始执行task64 === {number = 65, name = (null)}
11:08:42 开始执行task63 === {number = 66, name = (null)}

注意,此处打印时抽取的线程数number最大处摘选的几条打印结果:
从打印结果可以看到:XNU 内核仅仅使用了 60 多条线程来处理 1000个耗时任务,相对于比起串行队列的500多条线程,其对系统性能的消耗要远远优于串行队列。

3、什么是任务?

任务也称为作业的逻辑单元,可以由进程或者线程完成,在 GCD 中是放在 dispatch_block 或者dispatch_function中执行的;

typedef void (^dispatch_block_t)(void);
typedef void (*dispatch_function_t)(void *_Nullable);

执行任务有两种方式:同步执行dispatch_sync和异步执行dispatch_async:

//同步执行
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
//异步执行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
3.1、同步执行与异步执行
  • dispatch_sync()在调度队列中同步执行提交一个任务块,并堵塞当前线程,知道任务执行完毕才会接着向下执行。
  • dispatch_async ()在调度队列中异步执行提交一个任务块,并立即返回。

两个函数的参数都相同:

  • dispatch_queue_t queue 提交任务块的队列,系统将保留该队列,直到块运行完成为止。这个参数不能为空。
  • dispatch_block_t block 提交给目标分派队列的块,这个函数代表调用者执行Block_copyBlock_release。这个参数不能为空。

我们不妨先来看一段程序,再来总结“同步\异步执行”的区别:

3.1.1、同步执行dispatch_sync()
- (void)dispatch_sync_Method
{
    //创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
    });
    NSLog(@"------- 此处为taskA与taskB分界线 -------");
    dispatch_sync(queue, ^{
        NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
    });
}

我们在上述程序中执行了taskAtaskB两个耗时任务,使用dispatch_sync()函数同步执行,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:

09:30:34 -------- 开始处理 --------
09:30:34 开始执行:taskA ======= {number = 1, name = main}
09:30:37 结束执行:taskA ------- {number = 1, name = main}
09:30:37 ------- 此处为taskA与taskB分界线 -------
09:30:37 开始执行:taskB ======= {number = 1, name = main}
09:30:40 结束执行:taskB ------- {number = 1, name = main}
09:30:40 -------- 结束处理 --------
  • 通过对打印时间的观察,可以了解到程序的执行按顺序执行,只有前面的代码执行完毕,后面的代码才会接着执行,也就是说:dispatch_sync()同步添加任务到指定的队列中,在添加的任务执行结束之前,会阻塞当前的线程一直等待,直到队列里面的任务完成之后再继续执行;
  • 通过对当前线程的观察,可以了解到代码的执行都在主线程,也就是说:dispatch_sync()只能在当前线程(上下文所处线程)中执行任务,不具备开启新线程的能力;
3.1.2、异步执行dispatch_async()
- (void)dispatch_async_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
    });
    NSLog(@"------- 此处为taskA与taskB分界线 -------");
    dispatch_async(queue, ^{
        NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
    });
}

我们在上述程序中执行了taskAtaskB两个耗时任务,使用dispatch_async()函数异步执行,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:

09:31:09 -------- 开始处理 --------
09:31:09 ------- 此处为taskA与taskB分界线 -------
09:31:09 开始执行:taskA ======= {number = 3, name = (null)}
09:31:09 -------- 结束处理 --------
09:31:09 开始执行:taskB ======= {number = 4, name = (null)}
09:31:12 结束执行:taskB ------- {number = 4, name = (null)}
09:31:12 结束执行:taskA ------- {number = 3, name = (null)}
  • 通过对打印时间的观察,可以了解到程序的执行并没有按顺序执行,开始处理结束处理同时被打印,taskAtaskB同时执行,taskB并没有等待taskA执行完毕再去执行,这说明:dispatch_async()异步添加任务到指定的队列中,它不会阻塞当前的线程,可以继续执行任务;
  • 通过对任务执行线程的观察,可以了解到代码的执行在不同的线程中,也就是说:dispatch_sync()可以在新的线程中执行任务,具备开启新线程的能力;
3.1.3、同步执行与异步执行的区别

两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

同步执行 异步执行
同步添加任务到指定的队列中 异步添加任务到指定的队列中
在添加的任务执行结束之前,会阻塞当前的线程一直等待,直到队列里面的任务完成之后再继续执行 它不会阻塞当前的线程,可以继续执行任务
只能在当前线程中执行任务,不具备开启新线程的能力 可以在新的线程中执行任务,具备开启新线程的能力

注意:异步执行虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。

思考 :

  • dispatch_async() 函数如何实现,分发到主队列和全局队列有什么区别,一定会新建线程执行任务么?
  • dispatch_sync() 函数如何实现,为什么说 GCD 死锁是队列导致的而不是线程,死锁不是操作系统的概念么?
3.2、任务在dispatch_function中执行

我们在这里仅讨论下使用dispatch_async_f ()函数执行任务dispatch_function

 void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);

这个函数有三个参数:

  • 第一个参数queue:添加任务到指定的分派队列,这个参数不能为空。
  • 第二个参数void *_Nullable context:传递一个任意类型的参数至dispatch_function_t work
    注意:此处void *为C语言的不确定类型,类似于Objective-C 中的id类型。
  • 第三个参数dispatch_function_t workdispatch_function_t是一个函数指针,指向应用程序定义的函数,我们可以在这个函数中处理耗时操作,如读写数据、下载文件等;typedef void (*dispatch_function_t)(void *_Nullable);

我们以一段程序来看下它的使用:

void dispatch_async_function_test(void *_Nullable context)
{
    id object = (__bridge id)context;
    NSLog(@"开始处理 : %@ == %@",object,NSThread.currentThread);
    [NSThread sleepForTimeInterval:3];//模拟耗时任务
    NSLog(@"结束处理 : %@ == %@",object,NSThread.currentThread);
}

- (void)dispatch_async_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_SERIAL);
    dispatch_async_f(queue, @"这是一个任意类型的参数", dispatch_async_function_test);
}

运行程序:

09:48:25 开始处理 : 这是一个任意类型的参数 == 
09:48:28 结束处理 : 这是一个任意类型的参数 == 

通过打印,我们可以看到,dispatch_async_f()函数将函数指针dispatch_function_t work指向的函数交给分派队里queue处理,将第二个参数void *_Nullable context传递给函数指针指向的函数。
我们在函数指针指向的函数dispatch_async_function_test里处理了耗时任务。

3.3、任务在dispatch_block中执行

GCD 为我们提供了关于dispatch_block多个使用方法:

dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);
void dispatch_block_perform(dispatch_block_flags_t flags,DISPATCH_NOESCAPE dispatch_block_t block);
long dispatch_block_wait(dispatch_block_t block, dispatch_time_t timeout);
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue, dispatch_block_t notification_block);
void dispatch_block_cancel(dispatch_block_t block);
3.3.1、创建一个dispatch_block

我们可以使用函数dispatch_block_create()创建一个分派块,该函数有两个参数:

  • 第一个参数dispatch_block_flags_t flags:配置一个分派块
  • 第二个参数dispatch_block_t block:执行任务的块
  • 返回一个新的dispatch_block或者返回NULL

关于dispatch_block_flags_t

我们可以创建一个任务,如下所示:

    dispatch_block_t taskA = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
3.3.2、执行任务dispatch_block_perform ()
- (void)dispatch_block_perform_Method
{
    dispatch_block_t taskA = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
    dispatch_block_perform(DISPATCH_BLOCK_DETACHED, taskA);
}

我们来运行程序:

20:04:57 -------- 开始处理 --------
20:04:57 开始执行taskA === {number = 1, name = main}
20:05:00 结束执行taskA === {number = 1, name = main}
20:05:00 -------- 结束处理 --------

可以看到dispatch_block_perform ()函数执行了创建的dispatch_block

3.3.3、等待任务执行dispatch_block_wait()

函数long dispatch_block_wait()以同步方式等待,直到指定分派块的执行完成,或者直到指定的超时过去。它有两个参数:

  • 第一个参数dispatch_block_t block:等待的调度块
  • 第二个参数dispatch_time_t timeout:指定的截止时间

我们来看一个例子

- (void)dispatch_block_wait_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);

    dispatch_block_t taskA = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
    
    dispatch_block_t taskB = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
        NSLog(@"开始执行taskB === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskB === %@",NSThread.currentThread);
    });
    
    dispatch_block_t taskC = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
        NSLog(@"开始执行taskC === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskC === %@",NSThread.currentThread);
    });
    
    dispatch_async(queue, taskA);
    NSLog(@"执行 dispatch_block_wait");
    long  value = dispatch_block_wait(taskA, dispatch_time(DISPATCH_TIME_NOW,  (int64_t)(6 * NSEC_PER_SEC)));
    NSLog(@"dispatch_block_wait === %ld",value);
    if (value == 0)
    {
        NSLog(@"执行成功");
        dispatch_async(queue, taskB);
    }
    else
    {
        NSLog(@"执行超时");
        dispatch_async(queue, taskC);
    }
}

在上述程序中,我们创建了三个task,调用dispatch_block_wait()函数监听taskA的完成,根据返回值决定执行taskB或者taskC。运行程序:

20:18:09 -------- 开始处理 --------
20:18:09 执行 dispatch_block_wait
20:18:09 开始执行taskA === {number = 3, name = (null)}
20:18:12 结束执行taskA === {number = 3, name = (null)}
20:18:12 dispatch_block_wait === 0
20:18:12 执行成功
20:18:12 -------- 结束处理 --------
20:18:12 开始执行taskB === {number = 3, name = (null)}
20:18:15 结束执行taskB === {number = 3, name = (null)}

观察上述程序打印: 执行 dispatch_block_wait之后,直到结束执行taskA才打印dispatch_block_wait ===,这说明dispatch_block_wait()函数会堵塞当前线程的执行,直到等待的调度块完成或者等到指定的截止时间,线程才会接着向下执行;

3.3.4、监听任务完成dispatch_block_notify()

当指定分派块dispatch_block执行完成时提交给目标队列dispatch_queue一个通知块notification_block

void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue, dispatch_block_t notification_block);

该函数有三个参数:

  • 第一个参数dispatch_block_t block:被监听的任务块dispatch_block
  • 第三个参数dispatch_block_t notification_block:当观察到的块对象完成时要提交的通知块。
  • 第二个参数dispatch_queue_t queue:通知块被执行的目标队列;

我们来看一段程序:

- (void)dispatch_block_notify_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_block_t taskA = dispatch_block_create(DISPATCH_BLOCK_BARRIER, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
    
    dispatch_block_t taskB = dispatch_block_create(DISPATCH_BLOCK_BARRIER, ^{
        NSLog(@"开始执行taskB === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskB === %@",NSThread.currentThread);
    });
    
    dispatch_block_notify(taskA, queue, taskB);
    dispatch_async(queue, taskA);
}

在上述程序中,我们创建了taskAtaskB,调用dispatch_block_notify()函数监听taskA的完成,再执行taskB。运行程序:

20:26:49 -------- 开始处理 --------
20:26:49 -------- 结束处理 --------
20:26:49 开始执行taskA === {number = 4, name = (null)}
20:26:52 结束执行taskA === {number = 4, name = (null)}
20:26:52 开始执行taskB === {number = 4, name = (null)}
20:26:55 结束执行taskB === {number = 4, name = (null)}

观察上述程序打印:

  • 开始处理结束处理同时打印,这说明dispatch_block_notify()函数不会堵塞当前线程;
  • taskA执行完毕才去执行taskB
3.3.5、取消任务dispatch_block_cancel()
- (void)dispatch_block_cancel_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_block_t taskA = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
    
    dispatch_block_t taskB = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
        NSLog(@"开始执行taskB === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:2];//模拟耗时任务
        NSLog(@"结束执行taskB === %@",NSThread.currentThread);
    });

    dispatch_async(queue, taskA);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        dispatch_block_cancel(taskA);
    });
    dispatch_block_cancel(taskB);
    dispatch_async(queue, taskB);
}

在上述程序中,我们创建了taskAtaskB,调用dispatch_after()延迟取消taskA,调用dispatch_block_cancel()取消taskB。运行程序:

20:45:19 -------- 开始处理 --------
20:45:19 -------- 结束处理 --------
20:45:19 开始执行taskA === {number = 3, name = (null)}
20:45:22 结束执行taskA === {number = 3, name = (null)}

我们可以看到,程序只执行了taskA,并没有执行taskB;这说明dispatch_block_cancel()函数取消尚未执行的任务,但不影响已经在执行的任务块。

3.3.6、总结
方法 方法描述
dispatch_block_create() 创建一个任务块
dispatch_block_perform() 在当前线程执行一个任务块,会堵塞当前线程,没有开辟新线程的能力
dispatch_block_wait() 在指定的时间内等待一个任务块的完成;会堵塞当前线程;指定的时间内等待目标完成则返回0,否则返回一个非 0 值
dispatch_block_notify() 监听一个任务块完成的通知;不会堵塞当前线程
dispatch_block_cancel() 取消尚未执行的任务块;正在执行的任务块不会被取消

4、队列 + 任务

我们在上文讲了分派队列dispatch_queue的类型:串行队列、并发队列、主队列dispatch_get_main_queue();又讲了任务块dispatch_block_t或者任务函数dispatch_function_t可以同步执行也可以异步执行。那么将任务追加到队列里都有哪几种分类呢?它们如何处理?需要注意的问题有哪些呢?
我们不妨来一一探究下:

4.1、异步执行 + 并发队列
- (void)asyncTask_cocurrentQueue_Method
{
    //创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task0", DISPATCH_QUEUE_CONCURRENT);
    
    //将一个耗时任务A(异步)添加到并发队列 queue 里处理
    dispatch_async(queue, ^{
        NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
    });
    NSLog(@"------- 此处为taskA与taskB分界线 -------");
    //追加一个耗时任务B(异步)添加到 并发队列 queue 里处理
    dispatch_async(queue, ^{
        NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
    });
}

我们在上述程序中创建了并发队列DISPATCH_QUEUE_CONCURRENT;使用异步函数dispatch_async()函数执行分别将taskAtaskB两个耗时任务追加到并发队列中,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:

10:49:28 -------- 开始处理 --------
10:49:28 ------- 此处为taskA与taskB分界线 -------
10:49:28 开始执行:taskA ======= {number = 3, name = (null)}
10:49:28 -------- 结束处理 --------
10:49:28 开始执行:taskB ======= {number = 4, name = (null)}
10:49:31 结束执行:taskA ------- {number = 3, name = (null)}
10:49:31 结束执行:taskB ------- {number = 4, name = (null)}

观察 异步执行 + 并发队列 的打印:

  • 通过对打印时间的观察,可以了解到程序的执行并没有按顺序执行,开始处理结束处理同时被打印,taskAtaskB同时执行,taskB并没有等待taskA执行完毕再去执行,这说明:dispatch_async()异步添加任务到指定的队列中,它不会阻塞当前的线程,可以继续执行任务;
  • 通过对任务执行线程的观察,可以了解到代码的执行在不同的线程中,也就是说:dispatch_sync()可以在新的线程中执行任务,具备开启多条新线程的能力;
4.2、异步执行 + 串行队列
- (void)asyncTask_serialQueue_Method
{
    //创建一个串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task1", DISPATCH_QUEUE_SERIAL);
    
    //将一个耗时任务A(异步)添加到串行队列 queue 里处理
    dispatch_async(queue, ^{
        NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
    });
    NSLog(@"------- 此处为taskA与taskB分界线 -------");
    //追加一个耗时任务B(异步)添加到 串行队列 queue 里处理
    dispatch_async(queue, ^{
        NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
    });
}

我们在上述程序中创建了串行队列DISPATCH_QUEUE_SERIAL;使用异步函数dispatch_async()函数执行分别将taskAtaskB两个耗时任务追加到串行队列中,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:

10:24:25 -------- 开始处理 --------
10:24:25 ------- 此处为taskA与taskB分界线 -------
10:24:25 开始执行:taskA ======= {number = 3, name = (null)}
10:24:25 -------- 结束处理 --------
10:24:28 结束执行:taskA ------- {number = 3, name = (null)}
10:24:28 开始执行:taskB ======= {number = 3, name = (null)}
10:24:31 结束执行:taskB ------- {number = 3, name = (null)}

观察 异步执行 + 串行队列 的打印:

  • 通过对打印时间的观察,可以了解到程序的执行并没有按顺序执行,开始处理结束处理同时被打印,这说明:dispatch_async()异步添加任务到指定的队列中,它不会阻塞当前的线程,可以继续执行任务;
  • taskB等待taskA执行完毕再去执行,taskAtaskB处理任务的线程是同一线程。这说明:异步执行具备开启新线程的能力;但是串行队列只开启一个线程,串行队列中的多个任务,每次只有一个任务被执行,任务一个接一个按顺序执行
4.3、异步执行 + 主队列
- (void)asyncTask_mainQueue_Method
{
    //获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    //将一个耗时任务A(异步)添加到串行队列 queue 里处理
    dispatch_async(mainQueue, ^{
        NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
    });
    NSLog(@"------- 此处为taskA与taskB分界线 -------");
    //追加一个耗时任务B(异步)添加到 串行队列 queue 里处理
    dispatch_async(mainQueue, ^{
        NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
    });
}

我们在上述程序中获取了主队列dispatch_get_main_queue();使用异步函数dispatch_async()函数执行分别将taskAtaskB两个耗时任务追加到主队列中。
在此处:为什么我们是获取主队列dispatch_get_main_queue(),而不是创建了主队列?因为我们程序再启动时,系统就已经就已经创建好主队列了。
我们在各处打印日志,检测代码的执行时机;以下是控制台打印:

10:36:51 -------- 开始处理 --------
10:36:51 ------- 此处为taskA与taskB分界线 -------
10:36:51 -------- 结束处理 --------
10:36:51 开始执行:taskA ======= {number = 1, name = main}
10:36:54 结束执行:taskA ------- {number = 1, name = main}
10:36:54 开始执行:taskB ======= {number = 1, name = main}
10:36:57 结束执行:taskB ------- {number = 1, name = main}

观察 异步执行 + 主队列 的打印:

  • 通过对打印时间的观察,可以了解到程序的执行并没有按顺序执行,开始处理结束处理同时被打印,这说明:dispatch_async()异步添加任务到指定的队列中,它不会阻塞当前的线程,可以继续执行任务;
  • taskB等待taskA执行完毕再去执行,这再次说明了主队列就是串行队列的本质;串行队列中的多个任务,每次只有一个任务被执行,任务一个接一个按顺序执行。
  • taskAtaskB处理任务的线程是同一线程而且是主线程。这说明:虽然异步执行具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中;
4.4、同步执行 + 并发队列
- (void)syncTask_cocurrentQueue_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task4", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
    });
    NSLog(@"------- 此处为taskA与taskB分界线 -------");
    dispatch_sync(queue, ^{
        NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
    });
}

我们在上述程序中创建了并发队列DISPATCH_QUEUE_CONCURRENT;使用同步函数dispatch_sync()函数执行分别将taskAtaskB两个耗时任务追加到并发队列中,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:

10:58:46 -------- 开始处理 --------
10:58:46 开始执行:taskA ======= {number = 1, name = main}
10:58:49 结束执行:taskA ------- {number = 1, name = main}
10:58:49 ------- 此处为taskA与taskB分界线 -------
10:58:49 开始执行:taskB ======= {number = 1, name = main}
10:58:52 结束执行:taskB ------- {number = 1, name = main}
10:58:52 -------- 结束处理 --------

观察 同步执行 + 并发队列 的打印:

  • 通过对打印时间的观察,可以了解到程序的执行按顺序执行,只有前面的代码执行完毕,后面的代码才会接着执行,也就是说:dispatch_sync()同步添加任务到指定的队列中,在添加的任务执行结束之前,会阻塞当前的线程一直等待,直到队列里面的任务完成之后再继续执行;
  • 通过对当前线程的观察,可以了解到代码的执行都在主线程,也就是说:dispatch_sync()只能在当前线程(上下文所处线程)中执行任务,不具备开启新线程的能力;
  • taskB等待taskA执行完毕再去执行,这说明并发队列实际上并没有并发执行。因为dispatch_sync()不具备开启新线程的能力,也就不存在并发;
4.5、同步执行 + 串行队列
- (void)syncTask_serialQueue_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task5", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
    });
    NSLog(@"------- 此处为taskA与taskB分界线 -------");
    dispatch_sync(queue, ^{
        NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
    });
}

我们在上述程序中创建了串行队列DISPATCH_QUEUE_SERIAL;使用同步函数dispatch_sync()函数执行分别将taskAtaskB两个耗时任务追加到串行队列中,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:

11:10:47 -------- 开始处理 --------
11:10:47 开始执行:taskA ======= {number = 1, name = main}
11:10:50 结束执行:taskA ------- {number = 1, name = main}
11:10:50 ------- 此处为taskA与taskB分界线 -------
11:10:50 开始执行:taskB ======= {number = 1, name = main}
11:10:53 结束执行:taskB ------- {number = 1, name = main}
11:10:53 -------- 结束处理 --------

观察 同步执行 + 串行队列 的打印:

  • 通过对打印时间的观察,可以了解到程序的执行按顺序执行,只有前面的代码执行完毕,后面的代码才会接着执行,也就是说:dispatch_sync()同步添加任务到指定的队列中,在添加的任务执行结束之前,会阻塞当前的线程一直等待,直到队列里面的任务完成之后再继续执行;
  • 通过对当前线程的观察,可以了解到代码的执行都在主线程,也就是说:dispatch_sync()只能在当前线程(上下文所处线程)中执行任务,不具备开启新线程的能力;
4.6、同步执行 + 主队列

队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务

- (void)syncTask_mainQueue_Method
{
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_sync(mainQueue, ^{
        NSLog(@"开始执行:taskA ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskA ------- %@",NSThread.currentThread);
    });
    NSLog(@"------- 此处为taskA与taskB分界线 -------");
    dispatch_sync(mainQueue, ^{
        NSLog(@"开始执行:taskB ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:taskB ------- %@",NSThread.currentThread);
    });
}

我们在上述程序中获取了主队列dispatch_get_main_queue();使用同步函数dispatch_sync()函数执行分别将taskAtaskB两个耗时任务追加到主队列中。我们运行程序可以看到:在 Xcode 9 上线程死锁,直接异常终止:Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

OC之GCD_第1张图片
线程死锁(同步执行 + 主队列).png

在上面截图中,出错栈中的dispatch_sync_wait这个函数在wait什么?为什么会陷入wait无法离开?我们来分析上述代码:

  • 我们使用同步执行dispatch_sync ()函数向主队列mainQueue中添加了一个耗时任务;
    因为主队列mainQueue是一个串行队列,每次只有一个任务被执行,一个任务执行完毕后,再执行下一个任务。
  • 任务taskA被函数dispatch_sync ()添加至在队列mainQueue的末尾,taskA只有在队列mainQueue前面的任务执行完毕才会执行;因为队列是一种特殊的线性表,采用 FIFO(先进先出)的原则。
  • 同步dispatch_sync ()函数会阻塞当前线程的执行,直到队列mainQueue里面的任务完成之后再继续向下执行;
  • 这时问题来了,主队列mainQueue任务A堵塞,只有任务A处理完毕才会执行后面的代码;任务A被添加至队列mainQueue的末尾,只有mainQueue的任务处理完才会处理任务A;这不就两方在互相等待dispatch_sync_wait嘛?这也就造成了GCD死锁!
4.7、执行队列任务的小结
区别 并发队列 串行队列 主队列
异步执行 不会堵塞当前线程;可以开辟多条新线程;并发执行多个任务 不会堵塞当前线程;只开辟一条新线程;在新开辟的线程中串行执行多个任务 不会堵塞当前线程;没有开辟新线程; 在当前线程中串行执行多个任务
同步执行 堵塞当前线程;没有开辟新线程;在当前线程中串行执行多个任务 堵塞当前线程;没有开辟新线程;在当前线程中串行执行多个任务 GCD死锁

5、队列操作:暂停/继续

5.1、暂停/继续

当追加大量任务块到dispatch_queue时,在追加任务块的过程中,有时希望不执行已追加的任务块。这时,只要挂起dispatch_queue即可。当可以执行时再恢复。

//挂起指定的dispatch_queue
void dispatch_suspend(dispatch_object_t object);
//恢复指定的dispatch_queue
void dispatch_resume(dispatch_object_t object);

这些函数对已经执行的任务块没有影响。挂起后,追加到dispatch_queue中但尚未执行的任务块在此之后停止。而恢复则使得这些处理能够继续执行。
我们不妨来看一个例子:

- (void)dispatch_suspend_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"开始执行taskB === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskB === %@",NSThread.currentThread);
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        dispatch_suspend(queue);
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        dispatch_resume(queue);
    });
}

我们在上述程序创建了taskAtaskBdispatch_queue在执行1s后被dispatch_suspend挂起,10s后恢复;运行程序:

21:27:00 -------- 开始处理 --------
21:27:00 -------- 结束处理 --------
21:27:00 开始执行taskA === {number = 3, name = (null)}
21:27:03 结束执行taskA === {number = 3, name = (null)}
21:27:10 开始执行taskB === {number = 4, name = (null)}
21:27:13 结束执行taskB === {number = 4, name = (null)}

通过打印可以观察到:taskA 在开始3s后结束执行;taskB 没有在 taskA结束后立即执行,而是在开始处理 10s才开始执行;这表明 dispatch_suspend() 并不会立即暂停分派队列dispatch_queue中正在执行的任务,而是在当前任务执行完成后,暂停后续的任务执行。

5.2、更改目标队列.do_targetq

我们已经知道,dispatch_queue_t是一个结构体,其中有一个成员变量do_targetq!这个目标队列do_targetq是个很重要的概念:我们可以利用它更改这个结构体dispatch_queue_t的优先级,还能够创建队列的层次体系等等!

5.2.1、dispatch_queue_create()创建的dispatch_queue_t的成员do_targetq是什么?

我们不妨去GCD源码探究一下使用 dispatch_queue_create()创建的dispatch_queue_t的成员do_targetq是什么?

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
   dispatch_queue_t dq;
   size_t label_len;
   if (!label) {
       label = "";
   }

   label_len = strlen(label);//字符标签长度
   if (label_len < (DISPATCH_QUEUE_MIN_LABEL_SIZE - 1)) {
       label_len = (DISPATCH_QUEUE_MIN_LABEL_SIZE - 1);
   }

   dq = calloc(1ul, sizeof(struct dispatch_queue_s) -
   DISPATCH_QUEUE_MIN_LABEL_SIZE - DISPATCH_QUEUE_CACHELINE_PAD +
   label_len + 1);
   if (slowpath(!dq)) {
       return dq;
   }

   _dispatch_queue_init(dq);
   strcpy(dq->dq_label, label);

   if (fastpath(!attr))//苹果认为串行队列,或者 NULL 参数更常见,因此 !attr 的值很有可能不为 0,这与上文的结论一致。
   {
       return dq;
   }

   if (fastpath(attr == DISPATCH_QUEUE_CONCURRENT))
   {
       dq->dq_width = UINT32_MAX;
       dq->do_targetq = _dispatch_get_root_queue(0, false);

   } else {
       dispatch_debug_assert(!attr, "Invalid attribute");
   }
   return dq;
}

我们在上述函数中发现_dispatch_queue_init()生成了dispatch_queue_t队列,我们再去看下这个函数的实现:

static inline void _dispatch_queue_init(dispatch_queue_t dq)
{
   dq->do_vtable = &_dispatch_queue_vtable;
   dq->do_next = DISPATCH_OBJECT_LISTLESS;
   dq->do_ref_cnt = 1;
   dq->do_xref_cnt = 1;
   dq->do_targetq = _dispatch_get_root_queue(0, true);//0 是 DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级
   dq->dq_running = 0;
   dq->dq_width = 1;
   dq->dq_serialnum = dispatch_atomic_inc(&_dispatch_queue_serial_numbers) - 1;
}

此时,我们已经看到使用dispatch_queue_create()创建的dispatch_queue_t的成员do_targetq_dispatch_get_root_queue(0, true)!现在我们已经很接近答案了!我们接着去查看_dispatch_get_root_queue()函数的内部实现:

static inline dispatch_queue_t
_dispatch_get_root_queue(long priority, bool overcommit)
{
    if (overcommit) switch (priority) {
    case DISPATCH_QUEUE_PRIORITY_LOW:
        return &_dispatch_root_queues[
                DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_DEFAULT:
        return &_dispatch_root_queues[
                DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_HIGH:
        return &_dispatch_root_queues[
                DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_BACKGROUND:
        return &_dispatch_root_queues[
                DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY];
    }
    switch (priority) {
    case DISPATCH_QUEUE_PRIORITY_LOW:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_DEFAULT:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_HIGH:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_BACKGROUND:
        return &_dispatch_root_queues[
                DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY];
    default:
        return NULL;
    }
}

根据我们的入参(0, true),我们可以查找到函数执行的case为:

case DISPATCH_QUEUE_PRIORITY_DEFAULT:
    return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY];

此时,我们已经清晰的了解到:dispatch_queue_create()函数生成的队列dispatch_queue不管是串行队列DISPATCH_QUEUE_SERIAL,还是并发队列DISPATCH_QUEUE_CONCURRENT,都使用与默认优先级DISPATCH_QUEUE_PRIORITY_DEFAULT相同执行优先级的线程。

为山九仞,不可功亏一篑! 我们再去看看_dispatch_root_queues[]数组内部的元素到底是什么:

struct dispatch_queue_s _dispatch_root_queues[];

可以看到_dispatch_root_queues[]是一个内部包含struct dispatch_queue_s元素的数组;我们接着去看下这个结构数组的内部元素:(此处源码过长,我们仅摘取使用的case)

struct dispatch_queue_s _dispatch_root_queues[] = {

    [DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],
        .dq_label = "com.apple.root.default-overcommit-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 7,
    }
}

至此,我们已经刨根到底了!

GCD为我们提供了一个函数专门用来更改结构体dispatch_queue_t的成员变量do_targetq

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t _Nullable queue);

该函数有两个参数:

  • 第一个参数dispatch_object_t object:要修改的对象。这个参数不能为空;
  • 第一个参数dispatch_queue_t _Nullable queue:对象的新目标队列,这个参数不能为空。
5.2.2、更改队列优先级

dispatch_queue_create()函数生成的队列dispatch_queue不管是串行队列DISPATCH_QUEUE_SERIAL,还是并发队列DISPATCH_QUEUE_CONCURRENT,都使用与默认优先级DISPATCH_QUEUE_PRIORITY_DEFAULT相同执行优先级的线程。而使用dispatch_set_target_queue()函数,可以改变dispatch_queue_create()函数生成队列dispatch_queue的优先级。

5.2.3、创建队列的层次体系

dispatch_set_target_queue()函数除了能用来设置队列的优先级之外,还能够创建队列的层次体系,当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的target指向新创建的队列即可。

6、信号量dispatch_semaphore

dispatch_semaphoreNSCondition 类似,都是一种基于信号的同步方式。但 NSCondition 信号只能发送,不能保存(如果没有线程在等待,则发送的信号会失效);而 dispatch_semaphore 能保存发送的信号。dispatch_semaphore 的核心是 dispatch_semaphore_t类型的信号量,dispatch_semaphore 使用计数来实现该功能,计数为 0 时等待,计数为1或者大于 1 时,减去1而不等待。
我们可以使用 NSOperationQueue来直接控制线程的最大并发数量,但是我们如何在 dispatch_queue中控制线程的最大并发数?可以利用 GCD 的 dispatch_semaphore_t达到控制线程的最大并发数的目的!我们来看一段代码:

- (void)dispatch_semaphoreMethod
{
    //并发线程
    dispatch_queue_t queue = dispatch_queue_create("com.demo.maxCONCURRENT", DISPATCH_QUEUE_CONCURRENT);
    //创建一个semaphore,并设置最大信号量,最大信号量表示并发数
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_apply(6, queue, ^(size_t i) {
        long single = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        // 只有当信号量大于 0 的时候,线程将信号量减 1,程序向下执行
        // 否则线程会阻塞并且一直等待,直到信号量大于 0
        dispatch_async(queue, ^{
            NSLog(@"开始执行第 %zu 次任务 == single : %ld----- %@",i,single,NSThread.currentThread);
            [NSThread sleepForTimeInterval:(i % 2 == 0 ? 2 : 3)];//模拟耗时任务
            long value = dispatch_semaphore_signal(semaphore);// 当线程任务执行完成之后,发送一个信号,增加信号量。
            NSLog(@"结束执行第 %zu 次任务 == single : %ld----- %@",i,value,NSThread.currentThread);
        });
    });
}

在上述程序中,我们利用 dispatch_apply() 并发迭代了六次,使用dispatch_async()函数向 DISPATCH_QUEUE_CONCURRENT 分派队里追加了六个耗时任务异步执行;考虑到性能的优化,我们不希望无限制的开辟线程执行任务;这时可以使用 dispatch_semaphore_t 控制了线程的并发数量;
我们来看下打印结果:

20:24:14 -------- 开始处理 --------
20:24:14 开始执行第 1 次任务 == single : 0----- {number = 3, name = (null)}
20:24:14 开始执行第 0 次任务 == single : 0----- {number = 4, name = (null)}
20:24:16 结束执行第 0 次任务 == single : 1----- {number = 4, name = (null)}
20:24:16 开始执行第 2 次任务 == single : 0----- {number = 5, name = (null)}
20:24:17 结束执行第 1 次任务 == single : 1----- {number = 3, name = (null)}
20:24:17 开始执行第 3 次任务 == single : 0----- {number = 4, name = (null)}
20:24:18 开始执行第 4 次任务 == single : 0----- {number = 3, name = (null)}
20:24:18 结束执行第 2 次任务 == single : 1----- {number = 5, name = (null)}
20:24:20 开始执行第 5 次任务 == single : 0----- {number = 5, name = (null)}
20:24:20 结束执行第 4 次任务 == single : 1----- {number = 3, name = (null)}
20:24:20 结束执行第 3 次任务 == single : 0----- {number = 4, name = (null)}
20:24:23 结束执行第 5 次任务 == single : 0----- {number = 5, name = (null)}

我们可以看到: dispatch_queue 中同时执行任务的线程有两条。 因为我们使用 dispatch_semaphore_create() 创建了 dispatch_semaphore_t ,并赋予初值为 2

  • 我们在将任务添加至dispatch_queue之前执行 dispatch_semaphore_wait() 方法,减少一个信号;在任务执行结束之后,执行 dispatch_semaphore_signal()方法,增加一个信号。
  • 当执行第1次任务时,dispatch_semaphore 信号已经减少为0,此时dispatch_semaphore_wait()方法无法再减少信号继续执行,只能堵塞当前线程,导致第2次任务无法开始执行;
  • 开始处理 2s之后当第0次任务执行完毕,调用 dispatch_semaphore_signal() 方法,增加一个信号,此时执行 wait 的临界区唤醒线程执行后续代码,开始执行任务2,dispatch_semaphore 信号再次减少为0
  • 开始处理3s之后当第1次任务执行完毕,调用 dispatch_semaphore_signal()方法,增加一个信号,此时执行 wait 的临界区唤醒线程执行后续代码,开始执行任务3dispatch_semaphore信号再次减少为0
  • 后续的任务以此类推... 直至所有任务执行完毕。

从上面的代码可以看到,一个 dispatch_semaphore_wait(signal, overTime)方法会去对应一个 dispatch_semaphore_signal(signal); 看起来像NSLocklockunlock,区别只在于有信号量这个参数,lockunlock 只能同一时间,一个线程访问被保护的临界区,而如果 dispatch_semaphore 的信号量初始值为 value,则可以有value 个线程同时访问被保护的临界区。
总结:

dispatch_semaphore方法 方法描述
dispatch_semaphore_create(long value) 创建一个 dispatch_semaphore_t 类型的信号量,设定信号量的初始值为 value。注意:这里的传入的参数必须大于或等于 0,否则 dispatch_semaphore_create 会返回NULL
dispatch_semaphore_wait(signal, overTime); 判断signal的信号值value是否大于0。大于 0不会阻塞线程,value1,执行后续任务。如果信号值value0,该线程会和 NSCondition 一样直接进入 waiting 状态,等待其他线程发送信号唤醒线程去执行后续任务,或者当 overTime 时限到了,也会执行后续任务。
dispatch_semaphore_signal(signal); 使value值加一,发送信号,唤醒wait中的线程。

关于 dispatch_semaphore_wait(signal, overTime)的参数overTime:

  • DISPATCH_TIME_NOW 立即发生的时间,表示忽略信号量,直接运行。
  • DISPATCH_TIME_FOREVER表示无穷大的时间,表示会一直等待信号量为正数,才会继续运行

7、分派栅栏dispatch_barrier

分派栅栏可以在并发分派队列中创建同步点。当遇到dispatch_barrier时,并发队列会延迟dispatch_barrier块(或任何进一步的块)的执行,直到dispatch_barrier执行之前提交所有的块。此时,dispatch_barrier块将自动执行。完成后,队列恢复正常的执行行为。

//同步执行
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
//异步执行
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
7.1、同步栅栏 + 同步执行
- (void)dispatch_barrierSync_sync_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:1];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
    dispatch_sync(queue, ^{
        NSLog(@"开始执行taskB === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskB === %@",NSThread.currentThread);
    });
    NSLog(@"开始栅栏");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"开始执行taskC === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:2];//模拟耗时任务
        NSLog(@"结束执行taskC === %@",NSThread.currentThread);
    });
    
    dispatch_barrier_sync(queue, ^{
        NSLog(@"开始执行taskD === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:2];//模拟耗时任务
        NSLog(@"结束执行taskD === %@",NSThread.currentThread);
    });
    NSLog(@"结束栅栏");
    dispatch_sync(queue, ^{
        NSLog(@"开始执行taskE === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:4];//模拟耗时任务
        NSLog(@"结束执行taskE === %@",NSThread.currentThread);
    });
}

在上述程序中,我们使用dispatch_sync()函数向并发队列dispatch_queue添加了3个耗时任务taskAtaskBtaskE同步执行;taskC使用dispatch_after()函数延迟执行;taskD使用栅栏dispatch_barrier_sync()函数同步执行。我们运行程序:

09:55:29 -------- 开始处理 --------
09:55:29 开始执行taskA === {number = 1, name = main}
09:55:30 结束执行taskA === {number = 1, name = main}
09:55:30 开始执行taskB === {number = 1, name = main}
09:55:33 结束执行taskB === {number = 1, name = main}
09:55:33 开始栅栏
09:55:33 开始执行taskD === {number = 1, name = main}
09:55:35 结束执行taskD === {number = 1, name = main}
09:55:35 结束栅栏
09:55:35 开始执行taskE === {number = 1, name = main}
09:55:39 开始执行taskC === {number = 3, name = (null)}
09:55:39 结束执行taskE === {number = 1, name = main}
09:55:39 -------- 结束处理 --------
09:55:41 结束执行taskC === {number = 3, name = (null)}

观察打印结果:

  • 我们已经知道dispatch_sync()函数同步执行任务,会堵塞当前线程,所以taskAtaskB按顺序执行完毕,代码才会接着执行;dispatch_after()函数不堵塞当前线程,代码执行到开始栅栏处;
  • 结束执行taskB 6s之后,开始在新开辟的线程执行延迟函数dispatch_after()的任务块taskC
  • 我们可以看到结束执行taskD之后才执行结束栅栏,也就是说:dispatch_barrier_sync()函数同步执行任务,会堵塞当前线程;
7.2、同步栅栏 + 异步执行
- (void)dispatch_barrierSync_async_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:1];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"开始执行taskB === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskB === %@",NSThread.currentThread);
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"开始执行taskC === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:2];//模拟耗时任务
        NSLog(@"结束执行taskC === %@",NSThread.currentThread);
    });

    NSLog(@"开始栅栏");
    dispatch_barrier_sync(queue, ^{
        NSLog(@"开始执行taskD === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:2];//模拟耗时任务
        NSLog(@"结束执行taskD === %@",NSThread.currentThread);
    });
    NSLog(@"结束栅栏");
    dispatch_async(queue, ^{
        NSLog(@"开始执行taskE === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:4];//模拟耗时任务
        NSLog(@"结束执行taskE === %@",NSThread.currentThread);
    });
}

在上述程序中,我们使用dispatch_async()函数向并发队列dispatch_queue添加了3个耗时任务taskAtaskBtaskE异步执行;taskC使用dispatch_after()函数延迟执行;taskD使用栅栏dispatch_barrier_sync()函数同步执行。我们运行程序:

10:01:07 -------- 开始处理 --------
10:01:07 开始栅栏
10:01:07 开始执行taskB === {number = 5, name = (null)}
10:01:07 开始执行taskA === {number = 4, name = (null)}
10:01:08 结束执行taskA === {number = 4, name = (null)}
10:01:10 结束执行taskB === {number = 5, name = (null)}
10:01:10 开始执行taskD === {number = 1, name = main}
10:01:12 结束执行taskD === {number = 1, name = main}
10:01:12 结束栅栏
10:01:12 -------- 结束处理 --------
10:01:12 开始执行taskE === {number = 5, name = (null)}
10:01:13 开始执行taskC === {number = 6, name = (null)}
10:01:15 结束执行taskC === {number = 6, name = (null)}
10:01:16 结束执行taskE === {number = 5, name = (null)}

观察打印结果:

  • 我们已经知道dispatch_async()函数异步执行任务,不会堵塞当前线程,会开辟新的线程同步执行任务;所以taskAtaskB同时开始执行,延迟函数dispatch_after()开始栅栏处代码也同时执行;
  • 我们可以看到taskAtaskB两个任务都执行完毕之后才执行开始执行taskDtaskD执行完毕之后,才会接着执行taskE;也就是说:dispatch_barrier_sync()函数会堵塞当前线程,直到在它前面的目标队列dispatch_queue中的任务执行完毕,才会接着执行它的任务;它的任务执行完毕,代码才会接着向下执行;
  • dispatch_barrier_sync()函数与dispatch_sync()函数类似,执行代码块的线程都是当前线程(上下文所处的线程),并没有开辟新的线程;
7.3、异步栅栏 + 同步执行
- (void)dispatch_barrierAsync_sync_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:1];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"开始执行taskB === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskB === %@",NSThread.currentThread);
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"开始执行taskC === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:2];//模拟耗时任务
        NSLog(@"结束执行taskC === %@",NSThread.currentThread);
    });

    NSLog(@"开始栅栏");
    dispatch_barrier_async(queue, ^{
        NSLog(@"开始执行taskD === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:2];//模拟耗时任务
        NSLog(@"结束执行taskD === %@",NSThread.currentThread);

    });
    NSLog(@"结束栅栏");
    dispatch_sync(queue, ^{
        NSLog(@"开始执行taskE === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:4];//模拟耗时任务
        NSLog(@"结束执行taskE === %@",NSThread.currentThread);
    });
}

在上述程序中,我们使用dispatch_sync()函数向并发队列dispatch_queue添加了3个耗时任务taskAtaskBtaskE同步执行;taskC使用dispatch_after()函数延迟执行;taskD使用栅栏dispatch_barrier_async()函数异步执行。我们运行程序:

10:20:34 -------- 开始处理 --------
10:20:34 开始执行taskA === {number = 1, name = main}
10:20:35 结束执行taskA === {number = 1, name = main}
10:20:35 开始执行taskB === {number = 1, name = main}
10:20:38 结束执行taskB === {number = 1, name = main}
10:20:38 开始栅栏
10:20:38 结束栅栏
10:20:38 开始执行taskD === {number = 3, name = (null)}
10:20:40 结束执行taskD === {number = 3, name = (null)}
10:20:40 开始执行taskE === {number = 1, name = main}
10:20:44 开始执行taskC === {number = 3, name = (null)}
10:20:44 结束执行taskE === {number = 1, name = main}
10:20:44 -------- 结束处理 --------
10:20:46 结束执行taskC === {number = 3, name = (null)}

观察打印结果:

  • 我们已经知道dispatch_sync()函数同步执行任务,会堵塞当前线程,所以taskAtaskB按顺序执行完毕,代码才会接着执行;dispatch_after()函数不堵塞当前线程,代码执行到开始栅栏处;
  • 结束执行taskB 6s之后,开始在新开辟的线程执行延迟函数dispatch_after()的任务块taskC
  • 我们可以看到开始栅栏结束栅栏同时执行,也就是说:dispatch_barrier_async()函数不会堵塞当前线程;
  • taskEtaskD结束之后才开始执行,也就是说:dispatch_barrier_async()函数中的任务执行完毕,目标队列dispatch_queue中的任务taskE才会接着执行;
7.4、异步栅栏 + 异步执行
- (void)dispatch_barrierAsync_async_Method
{
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"开始执行taskA === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:1];//模拟耗时任务
        NSLog(@"结束执行taskA === %@",NSThread.currentThread);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"开始执行taskB === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行taskB === %@",NSThread.currentThread);
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"开始执行taskC === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:2];//模拟耗时任务
        NSLog(@"结束执行taskC === %@",NSThread.currentThread);
    });
    NSLog(@"开始栅栏");
    dispatch_barrier_async(queue, ^{
        NSLog(@"开始执行taskD === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:2];//模拟耗时任务
        NSLog(@"结束执行taskD === %@",NSThread.currentThread);
    });
    NSLog(@"结束栅栏");

    dispatch_async(queue, ^{
        NSLog(@"开始执行taskE === %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:4];//模拟耗时任务
        NSLog(@"结束执行taskE === %@",NSThread.currentThread);

    });
}

在上述程序中,我们使用dispatch_async()函数向并发队列dispatch_queue添加了3个耗时任务taskAtaskBtaskE异步执行;taskC使用dispatch_after()函数延迟执行;taskD使用栅栏dispatch_barrier_async()函数异步执行。我们运行程序:

10:38:49 -------- 开始处理 --------
10:38:49 开始栅栏
10:38:49 开始执行taskA === {number = 4, name = (null)}
10:38:49 开始执行taskB === {number = 5, name = (null)}
10:38:49 结束栅栏
10:38:49 -------- 结束处理 --------
10:38:50 结束执行taskA === {number = 4, name = (null)}
10:38:52 结束执行taskB === {number = 5, name = (null)}
10:38:52 开始执行taskD === {number = 5, name = (null)}
10:38:54 结束执行taskD === {number = 5, name = (null)}
10:38:54 开始执行taskE === {number = 5, name = (null)}
10:38:55 开始执行taskC === {number = 4, name = (null)}
10:38:57 结束执行taskC === {number = 4, name = (null)}
10:38:58 结束执行taskE === {number = 5, name = (null)}

观察打印结果:

  • 我们已经知道dispatch_async()函数异步执行任务,不会堵塞当前线程,会开辟新的线程同步执行任务;所以taskAtaskB同时开始执行,延迟函数dispatch_after()开始栅栏处代码也同时执行;
  • 我们可以看到开始栅栏结束栅栏同时执行,也就是说:dispatch_barrier_async()函数不会堵塞当前线程;
  • 我们可以看到taskAtaskB两个任务都执行完毕之后才执行开始执行taskDtaskD执行完毕之后,才会接着执行taskE;也就是说:dispatch_barrier_async()函数会堵塞它的目标队列dispatch_queue中的任务执行。首先执行目标队列dispatch_queue前面的任务,接着执行它的任务;它的任务执行完毕,代码才会接着向下执行;
  • dispatch_barrier_async()函数与dispatch_async()函数类似,开辟新的线程执行代码块;
7.5、dispatch_barrier_sync()函数与dispatch_barrier_async()函数的异同点:

dispatch_barrier_sync()函数与dispatch_barrier_async()函数的相同点:

  • 将目标队列dispatch_queue一分为2,分割处设置同步点;
  • 等待目标队列dispatch_queue同步点之前的任务块先执行完毕,才会执行该函数的任务块;
  • 处理目标队列dispatch_queue同步点上的任务块完毕之后,接着处理目标队列dispatch_queue同步点之后的任务;

dispatch_barrier_sync()函数与dispatch_barrier_async()函数的不同点:

dispatch_barrier_sync()函数 dispatch_barrier_async()函数
堵塞当前线程 不会堵塞当前线程
不会开辟新的线程处理任务块 开辟新的线程处理任务块

思考: 我们通过对taskC的执行时机可以看到,dispatch_barrier_sync()函数或者dispatch_barrier_async()函数对位于它前面的代码taskC并没有堵塞到,想一想这是为什么?开篇时我们说dispatch_barrier分派栅栏可以在并发分派队列中创建同步点,那么这个同步点位于何处?如何实现的同步点?

8、任务组dispatch_group

在我们日常开发中,经常会出现这样的需求:需要在追加到dispatch_queue中的多个任务执行完毕之后,去处理某件事情。比如:下载一个大的文件,分块下载,全部下载完成后再合成一个文件;再比如:同时下载多个图片,监听全部下载完后的动作等等很多场景。
如果使用一个Serial Dispatch Queue,将想执行的处理追加到该串行队列中并在最后追加结束处理即可,但是在使用Concurrent Queue时,可能会同时使用多个dispatch_queue时,源代码就会变得很复杂。
我们也可以使用分派栅栏dispatch_barrier在目标队列dispatch_queue中设置同步点,在同步点来处理这个棘手的事情;
Grand Central Dispath 为我们提供了解决方案dispatch_group。那么什么是dispatch_group?怎么使用dispatch_groupdispatch_group是怎么实现的?
我们先来看一下官方为我们提供的关于dispatch_group的API:

dispatch_group_t dispatch_group_create(void);
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_notify_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
8.1、dispatch_group_create ()创建的是什么东西?

我们阅读GCD源码可以看到dispatch_group_create ()的内部实现

dispatch_group_t
dispatch_group_create(void)
{
    return (dispatch_group_t)dispatch_semaphore_create(LONG_MAX);
}

dispatch_group_t其本质就是一个值为LONG_MAX的信号量dispatch_semaphore_t

8.2、dispatch_group_enter()dispatch_group_leave()

我们来看一下dispatch_group_enter()dispatch_group_leave()的使用:

- (void)dispatch_group_Enter_Leave_Method
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);

    dispatch_apply(3, queue, ^(size_t index) {
        dispatch_group_enter(group);//标志着一个任务追加到 group
        dispatch_async(queue, ^{
            NSLog(@"开始执行:task%zu ======= %@",index,NSThread.currentThread);
            [NSThread sleepForTimeInterval:3];//模拟耗时任务
            NSLog(@"结束执行:task%zu ------- %@",index,NSThread.currentThread);
            dispatch_group_leave(group);//标志着一个任务离开了 group
        });
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"****** 所有任务结束 ****** %@",NSThread.currentThread);
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"将要执行:task3  ======= %@",NSThread.currentThread);
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"开始执行:task3 ======= %@",NSThread.currentThread);
            [NSThread sleepForTimeInterval:3];//模拟耗时任务
            NSLog(@"结束执行:task3 ------- %@",NSThread.currentThread);
            dispatch_group_leave(group);//标志着一个任务离开了 group
        });
    });
}

在上述程序中,我们使用dispatch_async()函数向并发队列dispatch_queue添加了4个耗时任务异步执行;其中task310s之后才添加至并发队列dispatch_queue中;使用dispatch_group_notify()函数监听任务完成时的回调。我们运行程序:

21:25:40 -------- 开始处理 --------
21:25:40 -------- 结束处理 --------
21:25:40 开始执行:task0 ======= {number = 3, name = (null)}
21:25:40 开始执行:task2 ======= {number = 5, name = (null)}
21:25:40 开始执行:task1 ======= {number = 4, name = (null)}
21:25:43 结束执行:task1 ------- {number = 4, name = (null)}
21:25:43 结束执行:task0 ------- {number = 3, name = (null)}
21:25:43 结束执行:task2 ------- {number = 5, name = (null)}
21:25:43 ****** 所有任务结束 ****** {number = 1, name = main}
21:25:50 将要执行:task3  ======= {number = 1, name = main}
21:25:50 开始执行:task3 ======= {number = 5, name = (null)}
21:25:53 结束执行:task3 ------- {number = 5, name = (null)}

观察打印结果:

  • 开始处理\结束处理同时打印;
  • task0task1task2同时开始执行dispatch_group_enter(),将任务追加到dispatch_group
  • 每个任务完成后,都执行dispatch_group_leave()函数,将任务从dispatch_group释放;
  • 这三个任务全部完成后dispatch_group_notify()函数收到通知,触发该函数的第三个参数dispatch_block中的代码在第二个参数dispatch_queue中执行;
  • task3开始处理 10s后才开始执行,这个任务并没有被dispatch_group_notify()函数监听到!

思考:dispatch_group_notify()怎么监听到task0task1task2?为什么监听不到task3的完成?它的内部实现机制是什么?

8.3、dispatch_group_async()函数

我们来看下dispatch_group_async()函数的声明:

void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

该函数有三个参数:

  • 第一个参数dispatch_group_t group任务组:
  • 第二个参数dispatch_queue_t queue分派队列:
  • 第三个参数dispatch_block_t block处理任务的块:

我们来看一下dispatch_group_async()函数的使用:

- (void)dispatchGroupMethod
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_apply(3, queue, ^(size_t index) {
        dispatch_group_async(group, queue, ^{
            NSLog(@"开始执行:task%zu ======= %@",index,NSThread.currentThread);
            [NSThread sleepForTimeInterval:3];//模拟耗时任务
            NSLog(@"结束执行:task%zu ------- %@",index,NSThread.currentThread);
        });
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"****** 所有任务结束 ****** %@",NSThread.currentThread);
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"开始执行:task3 ======= %@",NSThread.currentThread);
        [NSThread sleepForTimeInterval:3];//模拟耗时任务
        NSLog(@"结束执行:task3 ------- %@",NSThread.currentThread);
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"将要执行:task4  ======= %@",NSThread.currentThread);
        dispatch_group_async(group, queue, ^{
            NSLog(@"开始执行:task4 ======= %@",NSThread.currentThread);
            [NSThread sleepForTimeInterval:3];//模拟耗时任务
            NSLog(@"结束执行:task4 ------- %@",NSThread.currentThread);
        });
    });
}

在上述程序中,我们使用dispatch_group_async()函数向并发队列dispatch_queue添加了5个耗时任务异步执行;其中task410s之后才添加至并发队列dispatch_queue中;使用dispatch_group_notify()函数监听dispatch_group任务完成时的回调。我们运行程序:

20:58:21 -------- 开始处理 --------
20:58:21 -------- 结束处理 --------
20:58:21 开始执行:task3 ======= {number = 6, name = (null)}
20:58:21 开始执行:task2 ======= {number = 5, name = (null)}
20:58:21 开始执行:task1 ======= {number = 4, name = (null)}
20:58:21 开始执行:task0 ======= {number = 3, name = (null)}
20:58:24 结束执行:task2 ------- {number = 5, name = (null)}
20:58:24 结束执行:task3 ------- {number = 6, name = (null)}
20:58:24 结束执行:task1 ------- {number = 4, name = (null)}
20:58:24 结束执行:task0 ------- {number = 3, name = (null)}
20:58:24 ****** 所有任务结束 ****** {number = 1, name = main}
20:58:31 将要执行:task4  ======= {number = 1, name = main}
20:58:31 开始执行:task4 ======= {number = 7, name = (null)}
20:58:34 结束执行:task4 ------- {number = 7, name = (null)}

观察打印结果:

  • 开始处理\结束处理同时打印,说明dispatch_group_async()函数确实异步执行,不会堵塞当前线程;
  • task0task1task2task3 同时开始执行;dispatch_group_notify()函数监听到这四个任务都执行完毕后,触发该函数的第三个参数dispatch_block中的代码在第二个参数dispatch_queue中执行
  • task4开始处理 10s后才开始执行,这个任务并没有被dispatch_group_notify()函数监听到!

我们可以看到:dispatch_group_async()函数的使用与dispatch_group_enter()dispatch_group_leave()dispatch_async()三个函数一起使用的效果完全一样;
思考:dispatch_group_async()函数内部是如何实现的呢?

8.4、了解dispatch_group_wait()
- (void)dispatch_group_waitMethod
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);

    dispatch_apply(3, queue, ^(size_t index) {
        dispatch_group_async(group, queue, ^{
            NSLog(@"开始执行:task%zu ======= %@",index,NSThread.currentThread);
            [NSThread sleepForTimeInterval:3];//模拟耗时任务
            NSLog(@"结束执行:task%zu ------- %@",index,NSThread.currentThread);
        });
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"将要执行:task3  ======= %@",NSThread.currentThread);
        dispatch_group_async(group, queue, ^{
            NSLog(@"开始执行:task3 ======= %@",NSThread.currentThread);
            [NSThread sleepForTimeInterval:3];//模拟耗时任务
            NSLog(@"结束执行:task3 ------- %@",NSThread.currentThread);
        });
    });
    
    NSLog(@"执行到此 ======= %@",NSThread.currentThread);
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}

在上述程序中,我们使用dispatch_group_async()函数向并发队列dispatch_queue添加了4个耗时任务异步执行;其中task310s之后才添加至并发队列dispatch_queue中;使用dispatch_group_wait()函数堵塞当前线程。我们运行程序:

21:11:29 -------- 开始处理 --------
21:11:29 执行到此 ======= {number = 1, name = main}
21:11:29 开始执行:task0 ======= {number = 3, name = (null)}
21:11:29 开始执行:task1 ======= {number = 4, name = (null)}
21:11:29 开始执行:task2 ======= {number = 5, name = (null)}
21:11:32 结束执行:task0 ------- {number = 3, name = (null)}
21:11:32 结束执行:task1 ------- {number = 4, name = (null)}
21:11:32 结束执行:task2 ------- {number = 5, name = (null)}
21:11:32 -------- 结束处理 --------
21:11:39 将要执行:task3  ======= {number = 1, name = main}
21:11:39 开始执行:task3 ======= {number = 5, name = (null)}
21:11:42 结束执行:task3 ------- {number = 5, name = (null)}

观察打印结果:

  • 开始处理\执行到此同时打印,说明dispatch_group_async()函数确实异步执行,不会堵塞当前线程;
  • task0task1task2 同时开始执行;这三个任务执行完毕后,结束处理才被打印,说明dispatch_group_wait()函数会堵塞当前线程,直到dispatch_group中的任务执行完毕,当前线程才会接着向下执行;
  • task4开始处理 10s后才开始执行,这个任务并没有被dispatch_group_wait()所堵塞!

思考:dispatch_group_wait()为什么堵塞不到task4?它能够堵塞dispatch_group中的任务的时机是何时?

9、延迟执行dispatch_after

9.1、不得不说的 dispatch_time_t
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

该函数有两个参数:

  • 第一个参数dispatch_time_t when:表示从什么时间开始,一般直接传 DISPATCH_TIME_NOW 表示从现在开始。
  • 第二个参数int64_t delta:表示具体的时间长度,delta的单位是纳秒,所以不能直接传 intfloat, 需要写成这种形式 (int64_t)3 * NSEC_PER_SEC

普及一下关键字:

缩写 全拼 汉译
MSEC millisecond 毫秒
NSEC nanoseconds 纳秒
USEC microsecond 微秒
SEC second
PER prep 每一

关于一些时间的宏:

含义
NSEC_PER_SEC 1000000000ull 每秒有10亿纳秒
NSEC_PER_MSEC 1000000ull 每毫秒有100万纳秒
USEC_PER_SEC 1000000ull 每秒有100万微秒
NSEC_PER_USEC 1000ull 每微秒有1000纳秒

我们使用 dispatch_time()函数来创建一个指定时间:

//从当前时间开始,10秒之后
dispatch_time_t tim1 = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC));
//从当前时间开始,10毫秒之后
dispatch_time_t time2 = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_MSEC));
//从当前时间开始,10微秒之后
dispatch_time_t time3 = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_USEC));
9.2、延迟执行dispatch_after

GCD 中关于在指定的时间执行一个任务的两个函数:

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);

在这里,我们仅讨论dispatch_after()函数,这个函数有三个参数:

  • 第一个参数dispatch_time_t when:指定将任务追加到队列中的时间
  • 第二个参数dispatch_queue_t queue:提交任务的队列,该队列由系统保留,直到任务运行到完成为止,这个参数不能为空。
  • 第三个参数dispatch_block_t block:任务块,这个参数不能为空。

我们来看一个程序:

- (void)dispatch_afterMethod
{
    NSLog(@"开始处理 ====== %@",NSThread.currentThread);

    dispatch_queue_t queue = dispatch_queue_create("dispatch_after", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
       
        NSLog(@"开始执行");
        // DISPATCH_TIME_NOW 当前时间开始
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC));
         // 在子线程延迟
        dispatch_after(time, queue, ^{
            NSLog(@"延迟十秒钟 === %@",NSThread.currentThread);
        });
        
        NSLog(@"结束执行");
    });
    
    NSLog(@"中间处理 ------ %@",NSThread.currentThread);
    
    //在主线程延迟
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"延迟十秒钟 --- %@",NSThread.currentThread);
    });
    
    NSLog(@"结束处理 ====== %@",NSThread.currentThread);
}

控制台输出结果:

18:44:49.622137+0800 开始处理 ====== {number = 1, name = main}
18:44:49.622505+0800 开始执行
18:44:49.622507+0800 中间处理 ------ {number = 1, name = main}
18:44:49.622749+0800 结束处理 ====== {number = 1, name = main}
18:44:49.622765+0800 结束执行
18:45:00.530441+0800 延迟十秒钟 --- {number = 1, name = main}
18:45:00.530525+0800 延迟十秒钟 === {number = 3, name = (null)}
18:45:05.971060+0800  threadExitNotice ------------ {number = 3, name = (null)}

注意:

  • 指定的时间dispatch_time_t when,不是在指定时间之后开始处理任务,而是在指定时间之后将任务追加到队列中。
  • 严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after()函数是很有效的。

10、快速迭代方法:dispatch_apply()

dispatch_apply()函数是dispatch_sync()函数和dispatch_group关联的API。该函数按指定的次数将指定的任务追加到指定的dispatch_queue中,并等待全部处理结束。

void dispatch_apply(size_t iterations, dispatch_queue_t queue, DISPATCH_NOESCAPE void (^block)(size_t));
void dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void *_Nullable context, void (*work)(void *_Nullable, size_t));

在这个仅讨论dispatch_apply()函数,该函数有三个入参:

  • 第一个参数size_t iterations:执行的迭代次数。
  • 第二个参数dispatch_queue_t queue:提交任务的队列,这个参数不能为空。
  • 第三个参数void (^block)(size_t):当前处理任务的代码块,其中size_t是迭代的当前索引

GCD 给我们提供了快速迭代的函数dispatch_apply() ,它类似一个for循环,会在指定的目标队列dispatch queue中运行block任务iterations次,如果队列是并发队列,则会并发执行block任务。我们来看下此函数的使用:

- (void)dispatch_applyMethod
{
    NSLog(@"开始处理");
    dispatch_queue_t queue = dispatch_queue_create("dispatch_apply", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"开始执行:%zd---%@",index, [NSThread currentThread]);
        [NSThread sleepForTimeInterval:(arc4random() % 6 + 1)];//模拟耗时任务
        NSLog(@"结束执行:%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"结束处理");
}

我们利用dispatch_apply()快速迭代六次,处理6个耗时任务,其中指定的目标队列dispatch queue为并发队列;来看下运行结果

13:49:58 开始处理
13:49:58 开始执行:0---{number = 1, name = main}
13:49:58 开始执行:1---{number = 3, name = (null)}
13:49:58 开始执行:2---{number = 4, name = (null)}
13:49:58 开始执行:3---{number = 5, name = (null)}
13:49:59 结束执行:1---{number = 3, name = (null)}
13:49:59 开始执行:4---{number = 3, name = (null)}
13:50:00 结束执行:2---{number = 4, name = (null)}
13:50:00 开始执行:5---{number = 4, name = (null)}
13:50:01 结束执行:0---{number = 1, name = main}
13:50:01 结束执行:3---{number = 5, name = (null)}
13:50:03 结束执行:5---{number = 4, name = (null)}
13:50:05 结束执行:4---{number = 3, name = (null)}
13:50:05 结束处理
  • 通过开始处理结束处理的打印时间可以知道:dispatch_apply()同步执行任务,等待全部队列的任务执行结束,才会接着向下执行;
  • 通过开始执行的时间可以看到:dispatch_apply()并发处理多个任务;

注意:由于dispatch_apply()是同步执行,所以目标队列不能是主队列,否则会造成线程死锁。

OC之GCD_第2张图片
dispatch_apply在主队列执行造成死锁.png

11、dispatch_once_t

GCD提供了dispatch_once()函数保证在应用程序执行中只执行一次指定处理。我们常用dispatch_once()函数生成 单例!

- (void)dispatch_once_Method
{
    static id shareInstance;
    static dispatch_once_t onceToken;
    dispatch_queue_t queue = dispatch_queue_create("com.demo.task", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(3, queue, ^(size_t index) {
        NSLog(@"开始执行:%zu ====== %@",index,NSThread.currentThread);
        dispatch_once(&onceToken, ^{
            if (!shareInstance) {
                shareInstance = [[NSObject alloc] init];
                [NSThread sleepForTimeInterval:3];
                NSLog(@"dispatch_once 执行内部 ====== %@",NSThread.currentThread);
                
            }
        });
        NSLog(@"结束执行:%zu ====== %@",index,NSThread.currentThread);
    });
}

打印结果:

14:05:17 开始执行:0 ====== {number = 1, name = main}
14:05:17 开始执行:1 ====== {number = 3, name = (null)}
14:05:17 开始执行:2 ====== {number = 4, name = (null)}
14:05:20 dispatch_once 执行内部 ====== {number = 1, name = main}
14:05:20 结束执行:0 ====== {number = 1, name = main}
14:05:20 结束执行:1 ====== {number = 3, name = (null)}
14:05:20 结束执行:2 ====== {number = 4, name = (null)}

如果在一个线程调用dispatch_once()函数时,另外的线程调用dispatch_once,则调用线程等待,直到首次调用dispatch_once()的线程返回。

资源帮助:
示例demo
苹果官方文档
GCD源码

参考文章:
深入理解GCD
iOS多线程:『GCD』详尽总结
iOS调度队列
GCD 之任务操作

你可能感兴趣的:(OC之GCD)