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比使用pthreads
和NSThread
这些一般的多线程编程API要好。
1.3、GCD 内存管理
当使用Objective-C构建应用程序时,GCD对象被称为dispatch object
,所有的dispatch objects
都是 Objective-C 对象。dispatch object
像Cocoa对象一样是引用计数的。因此,如果我们使用ARC,dispatch objects
将被保留并自动释放,就像任何其他Objective-C对象一样。当使用MRC时,需要使用dispatch_retain
和dispatch_release
函数来保留和释放分派对象,不能使用Core Foundation
框架的 retain
/release
函数。
思考:在GCD中是dispatch_object
是什么?如何将C语言的数据类型转为dispatch_object
?在ARC下,系统如何实现了dispatch_retain
和dispatch_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_queue
与 global_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_copy
和Block_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);
});
}
我们在上述程序中执行了taskA
、taskB
两个耗时任务,使用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);
});
}
我们在上述程序中执行了taskA
、taskB
两个耗时任务,使用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)}
- 通过对打印时间的观察,可以了解到程序的执行并没有按顺序执行,
开始处理
与结束处理
同时被打印,taskA
与taskB
同时执行,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 work
:dispatch_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);
}
在上述程序中,我们创建了taskA
、taskB
,调用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);
}
在上述程序中,我们创建了taskA
、taskB
,调用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()
函数执行分别将taskA
、taskB
两个耗时任务追加到并发队列中,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:
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)}
观察 异步执行 + 并发队列 的打印:
- 通过对打印时间的观察,可以了解到程序的执行并没有按顺序执行,
开始处理
与结束处理
同时被打印,taskA
与taskB
同时执行,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()
函数执行分别将taskA
、taskB
两个耗时任务追加到串行队列中,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:
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
执行完毕再去执行,taskA
与taskB
处理任务的线程是同一线程。这说明:异步执行具备开启新线程的能力;但是串行队列只开启一个线程,串行队列中的多个任务,每次只有一个任务被执行,任务一个接一个按顺序执行
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()
函数执行分别将taskA
、taskB
两个耗时任务追加到主队列中。
在此处:为什么我们是获取主队列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
执行完毕再去执行,这再次说明了主队列就是串行队列的本质;串行队列中的多个任务,每次只有一个任务被执行,任务一个接一个按顺序执行。 -
taskA
与taskB
处理任务的线程是同一线程而且是主线程。这说明:虽然异步执行具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中;
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()
函数执行分别将taskA
、taskB
两个耗时任务追加到并发队列中,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:
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()
函数执行分别将taskA
、taskB
两个耗时任务追加到串行队列中,我们在各处打印日志,检测代码的执行时机;以下是控制台打印:
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()
函数执行分别将taskA
、taskB
两个耗时任务追加到主队列中。我们运行程序可以看到:在 Xcode 9 上线程死锁,直接异常终止:Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
在上面截图中,出错栈中的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);
});
}
我们在上述程序创建了taskA
、taskB
;dispatch_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_semaphore
和 NSCondition
类似,都是一种基于信号的同步方式。但 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
的临界区唤醒线程执行后续代码,开始执行任务3
,dispatch_semaphore
信号再次减少为0
; - 后续的任务以此类推... 直至所有任务执行完毕。
从上面的代码可以看到,一个 dispatch_semaphore_wait(signal, overTime)
方法会去对应一个 dispatch_semaphore_signal(signal)
; 看起来像NSLock
的lock
和 unlock
,区别只在于有信号量这个参数,lock
、unlock
只能同一时间,一个线程访问被保护的临界区,而如果 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 不会阻塞线程,value 减1 ,执行后续任务。如果信号值value 为 0 ,该线程会和 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个耗时任务taskA
、taskB
、taskE
同步执行;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()
函数同步执行任务,会堵塞当前线程,所以taskA
、taskB
按顺序执行完毕,代码才会接着执行;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个耗时任务taskA
、taskB
、taskE
异步执行;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()
函数异步执行任务,不会堵塞当前线程,会开辟新的线程同步执行任务;所以taskA
、taskB
同时开始执行,延迟函数dispatch_after()
与开始栅栏
处代码也同时执行; - 我们可以看到
taskA
、taskB
两个任务都执行完毕之后才执行开始执行taskD
;taskD
执行完毕之后,才会接着执行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个耗时任务taskA
、taskB
、taskE
同步执行;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()
函数同步执行任务,会堵塞当前线程,所以taskA
、taskB
按顺序执行完毕,代码才会接着执行;dispatch_after()
函数不堵塞当前线程,代码执行到开始栅栏
处; -
结束执行taskB 6s
之后,开始在新开辟的线程执行延迟函数dispatch_after()
的任务块taskC
; - 我们可以看到
开始栅栏
与结束栅栏
同时执行,也就是说:dispatch_barrier_async()
函数不会堵塞当前线程; -
taskE
在taskD
结束之后才开始执行,也就是说: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个耗时任务taskA
、taskB
、taskE
异步执行;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()
函数异步执行任务,不会堵塞当前线程,会开辟新的线程同步执行任务;所以taskA
、taskB
同时开始执行,延迟函数dispatch_after()
与开始栅栏
处代码也同时执行; - 我们可以看到
开始栅栏
与结束栅栏
同时执行,也就是说:dispatch_barrier_async()
函数不会堵塞当前线程; - 我们可以看到
taskA
、taskB
两个任务都执行完毕之后才执行开始执行taskD
;taskD
执行完毕之后,才会接着执行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_group
?dispatch_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个耗时任务异步执行;其中task3
在10s
之后才添加至并发队列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)}
观察打印结果:
-
开始处理\结束处理
同时打印; -
task0
、task1
、task2
同时开始执行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()
怎么监听到task0
、task1
、task2
?为什么监听不到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个耗时任务异步执行;其中task4
在10s
之后才添加至并发队列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()
函数确实异步执行,不会堵塞当前线程; -
task0
、task1
、task2
、task3
同时开始执行;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个耗时任务异步执行;其中task3
在10s
之后才添加至并发队列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()
函数确实异步执行,不会堵塞当前线程; -
task0
、task1
、task2
同时开始执行;这三个任务执行完毕后,结束处理
才被打印,说明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
的单位是纳秒,所以不能直接传int
或float
, 需要写成这种形式(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()
是同步执行,所以目标队列不能是主队列,否则会造成线程死锁。
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 之任务操作