0.前言
0.什么是GCD?
GCD全程为Grand Central Dispatch,是异步执行的技术之一。一般将应用程序中记述的线程管理代码在系统级实现。开发者只用定义向执行的任务,并追加到适当的Dispatch Queue中就完事儿了,GCD能自动生成必要的线程并执行任务。
1.单线程
CPU一次只能执行一个命令 ,不能一心两用,同时执行两个分开的并列命令,因此通过CPU执行命令就像是一条五分叉的路,及时地址分散,看似凌乱,实则也只有一条路径。这就是线程。
2.上下文切换
macOS和iOS中的核心XNU内核在发生操作系统事件时,会切换执行路径,执行中路径的状态,会被保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令列。这就是上下文切换。
3.为什么长时间的处理不放在主线程执行?
程序在启动时,最先执行的线程是主线程,用来描绘用户界面、处理屏幕触摸的事件。如果主线程进行长时间的处理,机会导致主线程阻塞,妨碍主线程中RunLoop主循环的执行,导致不能更新用户界面、应用画面长时间停滞等问题。
1.GCD的API
1. DIspatch Queue
名词解释
官方对GCD的说明:
开发者要做的只是定义想执行的任务,并追加到适当的Dispatch Queue中。
源码的表示就是:
dispatch_sync(queue, ^{
//your work here;
});
[4] → | [3] [2] [1] | → [0]
追加 Dispatch Queue 已处理
Dispatch Queue遵循FIFO原则,先进先出。
Dispatch Queue的种类
- Serial Dispatch Queue :串行,一个线程
- Concurrent Dispatch Queue :并行,多线程
2.如何得到Dispatch Queue
1.通过CGD的API生成
以下代码生成了Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.MyGCDSerialDispatchQueue", NULL);
以下代码生成了Concurrent Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.myGCDConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
关于Serial Dispatch Queue的个数
一个Serial Dispatch Queue同时只能追加一个处理。
多个Serial DIspatch Queue各追加处理,并同时执行,就变成并行执行。
注意,一旦生成1个Serial DIspatch Queue,系统就对一个Queue生成并使用一个线程,如果生成2000个,那就生成2000个线程。
因此,只是在为了避免多个线程更新相同资源导致的数据竞争时使用Serial DIspatch Queue
关于dispatch_queue_create
方法
dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t _Nullable attr)
第一个参数为queue的名称,推荐输入逆序全程域名,崩溃时会出现在log中。
第二个参数为queue的类型,如果输入NULL
则为Serial,如果输入DISPATCH_QUEUE_CONCURRENT
则为Concurrent。
另:iOS6之后,DIspatch Queue也加入了ARC,如果手动设置Retain和Release,会报错。
将Block内的执行追加到Queue中
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.myGCDConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"v-tech is the BEST!");
});
2.获取系统标准提供的Dispatch Queue
系统提供的Dispatch Queue有:
- Main Dispatch Queue
- Global Dispatch Queue
Main Dispatch Queue是在主线程执行的Dispatch Queue,只有一个,是Serial Dispatch Queue。追加到Main Dispatch Queue的处理在主线程的RunLoop中执行,因此必须将用户界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue中。
Global Dispatch Queue是所有程序都可以使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create
逐个生成Concurrent Dispatch Queue,直接获取Global Dispatch Queue就行了。
获取Main Dispatch Queue和 Global Dispatch Queue的方法
//main
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
//global
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(long identifier, unsigned long flags);
//global example
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
获取Main Dispatch Queue的方法只有一个。
获取Global的方法里有两个变量,第一个是设定优先级,有以下四个可以选择:
第二个参数传入0即可。
文档关于dispatch_get_global_queue
的介绍:
/*!
* @function dispatch_get_global_queue
*
* @abstract
* Returns a well-known global concurrent queue of a given quality of service
* class.
*
* @discussion
* The well-known global concurrent queues may not be modified. Calls to
* dispatch_suspend(), dispatch_resume(), dispatch_set_context(), etc., will
* have no effect when used with queues returned by this function.
*
* @param identifier
* A quality of service class defined in qos_class_t or a priority defined in
* dispatch_queue_priority_t.
*
* It is recommended to use quality of service class values to identify the
* well-known global concurrent queues:
* - QOS_CLASS_USER_INTERACTIVE
* - QOS_CLASS_USER_INITIATED
* - QOS_CLASS_DEFAULT
* - QOS_CLASS_UTILITY
* - QOS_CLASS_BACKGROUND
*
* The global concurrent queues may still be identified by their priority,
* which map to the following QOS classes:
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
*
* @param flags
* Reserved for future use. Passing any value other than zero may result in
* a NULL return value.
*
* @result
* Returns the requested global queue or NULL if the requested global queue
* does not exist.
*/
3.延迟处理
场景:在3秒后将指定的Block追加到Main Dispatch Queue
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"wait at least 3 second");
});
注意,代码指的不是3秒后执行Block内的处理,而是在3秒后将执行追加到Queue中执行。
因为Main Dispatch Queue 在主线程的RunLoop中执行,所以在比如每隔1/60秒执行的RunLoop中,Block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理追加或者主线程的处理本身有延迟,这个时间会更长。
dispatch_time
方法解析
dispatch_time(dispatch_time_t when, int64_t delta)
第一个参数是时间点,DISPATCH_TIME_NOW
意思是现在。
第二个参数是距离时间点的长度,也就是多少时间之后追加到Queue中,格式如下:
3ull * NSEC_PER_SEC
/*
* 3ull * NSEC_PER_SEC 表示3秒
*
* ull = unsigned long long
*
* #define NSEC_PER_SEC 1000000000ull
* #define NSEC_PER_MSEC 1000000ull
* #define USEC_PER_SEC 1000000ull
* #define NSEC_PER_USEC 1000ull
*
*/
4.等待所有任务完成后执行其他处理
1.Dispatch Group
场景:在追加到Dispatch Queue中的多个处理全部结束之后执行其他处理。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"other method");
});
dispatch_group_create()
生成dispatch_group_t
类的Dispatch Group。
dispatch_group_async
与dispatch——async
相同,都是追加Block到指定的Dispatch Queue中。不同的是,指定的Block属于指定的Dispatch Group。
在追加到Dispatch Group中的处理全部执行结束后,该源代码中使用的dispatch_group_notify
函数会将执行的Block追加到Dispatch Queue中,将第一个参数指定为要监视的Dispatch Group。在追加到改Group的全部处理执行解释后,将第三个参数的Block追加到第二个参数的Dispatch Queue中。在dispatch_group_notify
中不管制定什么样的Dispatch Queue,属于Dispatch Group的全部处理在追加指定的Block时已经结束。
dispatch_group_async
的介绍
/*!
* @function dispatch_group_async
*
* @abstract
* Submits a block to a dispatch queue and associates the block with the given
* dispatch group.
*
* @discussion
* Submits a block to a dispatch queue and associates the block with the given
* dispatch group. The dispatch group may be used to wait for the completion
* of the blocks it references.
*
* @param group
* A dispatch group to associate with the submitted block.
* The result of passing NULL in this parameter is undefined.
*
* @param queue
* The dispatch queue to which the block will be submitted for asynchronous
* invocation.
*
* @param block
* The block to perform asynchronously.
*/
2.Dispatch Group Wait
在Group中也可以使用dispatch_group_wait
函数仅等待全部处理执行结束。
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
forever的意思是永远等待,只要Group里面的处理尚未执行,就会一直等待。
如同dispatch_after
一样,制定等待间隔为1秒的处理如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if(result == 0){
//属于Dispatch Group的处理全部执行结束
NSLog(@"done");
}else{
//属于Dispatch Group的某一个处理还在进行 或者 超时了
NSLog(@"doing");
}
其中,dispatch_group_wait(group, time)
中的time是time-ou时间,如果到了time制定的时间还没有完成处理,就直接返回非零值,表示超时了。
如果不想等待,直接判定,可以把时间设定为DISPATCH_TIME_NOW
。如果要一直等待,就设定为DISPATCH_TIME_FOREVER
。
3.notify
和wait
在主线程RunLoop的每次循环中,可检查执行是否结束,从而不耗费多余的等待时间,虽然这样也可以,但一般这种情况下,还是推荐使用notify
,函数追加结束处理到Main Dispatch Queue中们可以简化源代码。
5.处理写入处理的数据竞争
写入处理不能并行执行,会引发数据竞争,但是读取处理可以,因此,为了高效访问,读取处理可以追加到Concurrent Dispatch Queue,写入处理可以在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中(写入处理结束之前,读取处理不可执行)。
使用dispatch_barrier_async
函数
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"read 0");
});
dispatch_async(queue, ^{
NSLog(@"read 1");
});
dispatch_async(queue, ^{
NSLog(@"read 2");
});
dispatch_async(queue, ^{
NSLog(@"read 3");
});
/*
* 写入处理
*/
dispatch_barrier_async(queue, ^{
NSLog(@"write");
});
dispatch_async(queue, ^{
NSLog(@"read 4");
});
dispatch_async(queue, ^{
NSLog(@"read 5");
});
dispatch_async(queue, ^{
NSLog(@"read 6");
});
dispatch_async(queue, ^{
NSLog(@"read 7");
});