说到CGD,我们先来了解一下容易混淆的概念:
![](https://img.haomeiwen.com/i3512027/a2d1bb64a2a912f0.png)
最纠结的问题之一:什么是并行
多个线程可以同时执行多个任务的处理便为“并行执行”。
并行是并发的一种特殊情况,并行的前提是并发,并发不一定是并行。
串行队列和并行队列都有“并行执行”的情况。如果多个串行队列在各自线程同时各执行任务也是并行执行(其实也是多线程并发执行任务)。
关于串行、 并发、 并行借自己的生活场景以及自己的理解来打个比方:
家里有两个煤气灶台,一个电磁炉。
串行:电磁炉做菜,我做完一个菜之后才可以做第二个菜。
并发:煤气灶做菜,我不用管第一个灶台做没做完了,就打开第二个煤气灶做菜了(好比开启新线程)。
并行:两个煤气灶同时在做菜。当然电磁炉也可以出现并行的情况,再买个电磁炉,2个电磁炉单独做菜也可以达到并行的效果了。(不过酱紫十分消耗资源阿)
GCD队列类型的区别
![](https://img.haomeiwen.com/i3512027/fa843cebf1776ecd.png)
Dispatch Queue
Dispatch Queue是执行处理的等待队列。遵循先进先出(FIFO,first-in-first-out)的原则。
Dispatch Queue的执行任务方式有两种:
![](https://img.haomeiwen.com/i3512027/53ecc340f9235730.png)
在dispatch_async中追加处理时,源代码输出两种Queue的区别:
dispatch_async(queue,blk0);
dispatch_async(queue,blk1);
dispatch_async(queue,blk2);
dispatch_async(queue,blk3);
dispatch_async(queue,blk4);
dispatch_async(queue,blk5);
当queue是Serial Dispatch Queue时,执行顺序为:
blk0
blk1
blk2
blk3
blk4
blk5
当queue是Concurrent Dispatch Queue时,假设准备3个Concurrent Dispatch Queue开启线程执行顺序为:
![](https://img.haomeiwen.com/i3512027/1cb1d4b403b68517.png)
由两者比较可以看出,在Concurrent Dispatch Queue执行处理时,执行顺序会根据处理内容和系统状态发生改变,不同于执行顺序固定的Serial Dispatch Queue。
得到Dispatch_Queue的方法有两种:一种是通过dispatch_queue_create创建,一种是系统提供的Main Dispatch Queue 和 Global Dispatch Queue。
dispatch_queue_create
可以通过dispatch_queue_create函数创建串行/并发队列。
避免多个线程更新相同资源导致数据竞争时使用串行队列。想并行执行不发生数据竞争等问题的处理时使用并发队列。
创建串行队列:
dispatch_queue_t queue = dispatch_queue_create("me.rose99.blog", NULL);
dispatch_async(queue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
创建并行队列也是如此,只是把第二个参数改为DISPATCH_QUEUE_CONCURRENT的宏即可。
Main Dispatch Queue/Global Dispatch Queue
Main Dispatch Queue是在主线程中执行的队列,因为主线程(用户界面更新必须要放在主线程中执行)只有一个,所以是串行队列。
Global Dispatch Queue是所有应用程序都能使用的并发队列。Global Dispatch Queue有4个执行优先级:
![](https://img.haomeiwen.com/i3512027/6805aa133ae51a1e.png)
获取方法如下:
//主队列的获取方法
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
//全局并发队列的获取方法
dispatch_queue_t gobalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(gobalDispatchQueue, ^{
//要执行的操作
dispatch_async(mainDispatchQueue, ^{
//只能在主线程中执行的操作
});
});
变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数,如果多个串行队列中用dispatch_set_target_queue函数指定目标为串行队列,可防止处理并行执行。因为原本应并行执行的多个串行队列,在目标队列上只能同时执行一个处理。
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("me.rose99.blog", NULL);
dispatch_queue_t gobalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//变更执行优先级,第一个参数是要变更的优先级的queue,第二个参数是目标queue(与执行优先级相同的queue)
dispatch_set_target_queue(serialDispatchQueue, gobalDispatchQueueBackground);
dispatch_after
想在3秒后执行处理可使用dispatch_after函数来实现。
/*
dispatch_time_t 是dispatch_time函数/dispatch_walltime的返回值
第一个参数:指定的时间开始 DISPATCH_TIME_NOW 是指现定从现在的时间开始
第二个参数:指定的结束时间 3ull *NSEC_PER_SEC 是开始3秒后的dispatch_time_t类型的值, 3NSEC_PER_MSEC 是开始3毫秒后的值 ull是数值字面量(显示表名类型时使用的字符串unsigned long long)
*/
//获取从现在的时间开始到3秒后时间的值
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull *NSEC_PER_SEC);
/*
dispatch_after不是指定时间后执行,而是在指定的时间追加处理到Dispatch Queue。
因为Main Dispatch Queue 在主线程的Runloop中执行,若在每隔1/60秒执行的Runloop中,block最快在3秒后执行,最慢在3+1/60秒后执行。不过想大概延迟处理时,该函数还是很有效的
*/
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited time at least three seconds.");
});
dispatch_time函数通常用于计算相对时间,dispatch_walltime函数用于计算绝对时间。
比如想在dispatch_after函数中指定2016年11月11号11分11秒这一绝对时间的情况,可以使用dispatch_walltime函数。
NSDate类对象可转换成dispatch_time_t类型的值传递给dispatch_after函数。如下:
dispatch_time_t getDispatchTimeByDate(NSDate *date)
{
NSTimeInterval interval;
double second,subsecond;
struct timespec time;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = sub second * NSEC_PER_SEC;
milestone = dispatch_wait(&time, 0);
return milestone;
}
Dispatch Group
若想在多个任务异步处理结束后在处理别的任务的情况可以使用Dispatch Group。(比如:我点击个按钮,开启两个异步下载图片的任务,等这两个任务结束后想更新UI)
//创建并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建队列组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
//异步下载第一张图
});
dispatch_group_async(group, queue, ^{
//异步下载第二张图
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//主线程更新UI
});
//指定等待间隔1秒时做任务处理
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_wait(group, time);
if (result == 0) {
//1秒全部处理结束
}
else
{
//还有图片没有下载完成。
}
dispatch_group_notify函数追加的Block是在Dispatch Group的全部处理之后。
dispatch_group_wait函数用来处理仅等待全部处理任务结束(在指定时间或者指定的Dispatch Group处理任务结束之前,只调用不返回,若指定时间为DISPATCH_TIME_NOW效果和dispatch_group_notify函数相同)。
dispatch_barrier_async(栅栏函数)
dispatch_barrier_async函数的作用:
- 避免数据竞争
- 可实现高效率的数据库访问和文件访问
dispatch_barrier_async 需要与__ dispatch_queue_create__创建的Concurrent Dispatch Queue一起使用。
dispatch_queue_t queue = dispatch_queue_create("me.rose99.blog", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"🍎");
});
dispatch_async(queue, ^{
NSLog(@"🍎🍎");
});
dispatch_barrier_async(queue, ^{
NSLog(@"🍊");
});
dispatch_async(queue, ^{
NSLog(@"🍎🍎🍎");
});
dispatch_async(queue, ^{
NSLog(@"🍎🍎🍎🍎");
});
输出结果:
2016-12-14 17:59:58.898 Dispatch_Queue[4555:232866] 🍎🍎
2016-12-14 17:59:58.898 Dispatch_Queue[4555:232867] 🍎
2016-12-14 17:59:58.898 Dispatch_Queue[4555:232867] 🍊
2016-12-14 17:59:58.898 Dispatch_Queue[4555:232867] 🍎🍎🍎
2016-12-14 17:59:58.898 Dispatch_Queue[4555:232866] 🍎🍎🍎🍎
__ dispatch_barrier_async__ 函数是等待前面的所有异步函数处理完毕之后才会执行,__ dispatch_barrier_async__之后的函数等待dispatch_barrier_async函数之后才会执行。因为是异步,所以1个苹果和2个苹果的执行完毕顺序有可能不同(同理3个和4个也一样)。
如果是系统提供的Main Dispatch Queue 或者Global Dispatch Queue , Main Dispatch Queue的执行顺序就是依次执行,Global Dispatch Queue 的执行顺序就和dispatch_barrier_async函数和dispatch_async函数就没什么区别了。
// 换成Main Dispatch Queue 输出结果:
2016-12-14 18:26:00.674 Dispatch_Queue[5259:258557] 🍎
2016-12-14 18:26:00.674 Dispatch_Queue[5259:258557] 🍎🍎
2016-12-14 18:26:00.674 Dispatch_Queue[5259:258557] 🍊
2016-12-14 18:26:00.674 Dispatch_Queue[5259:258557] 🍎🍎🍎
2016-12-14 18:26:00.675 Dispatch_Queue[5259:258557] 🍎🍎🍎🍎
//换成Global Dispatch Queue 输出结果:
2016-12-14 18:28:07.519 Dispatch_Queue[5328:261570] 🍎
2016-12-14 18:28:07.519 Dispatch_Queue[5328:261553] 🍊
2016-12-14 18:28:07.519 Dispatch_Queue[5328:261550] 🍎🍎
2016-12-14 18:28:07.519 Dispatch_Queue[5328:261551] 🍎🍎🍎
2016-12-14 18:28:07.519 Dispatch_Queue[5328:261583] 🍎🍎🍎🍎
死锁
相互等待执行处理便会造成死锁。例如:
//在主线程中调用同步主队列造成死锁
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"🍌🍌🍌");
});
//主线程要执行的Block等待主线程执行的Block执行完毕造成死锁
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"🍌🍌🍌");
});
});
dispatch_apply
__ dispatch_apply__函数与同步函数相同,都会等待全部任务执行结束。
//获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
第一个参数为重复次数
第二个参数为追加对象的queue
第三个是要执行的block,block带有参数索引值为了区分重复追加的多个block。
*/
dispatch_apply(7, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"🍌🍌🍌");
输出结果:
2016-12-15 14:09:22.730 GCD[14332:136228] 0
2016-12-15 14:09:22.730 GCD[14332:136444] 1
2016-12-15 14:09:22.730 GCD[14332:136464] 3
2016-12-15 14:09:22.730 GCD[14332:136446] 2
2016-12-15 14:09:22.730 GCD[14332:136228] 4
2016-12-15 14:09:22.730 GCD[14332:136444] 5
2016-12-15 14:09:22.731 GCD[14332:136464] 6
2016-12-15 14:09:22.731 GCD[14332:136228] 🍌🍌🍌
由于是并发队列,各个处理的执行时间不定,但🍌🍌🍌一定是最后输出,因为dispatch_apply函数会等待全部任务执行结束。如果是串行队列,处理会按照顺序依次执行。
dispatch_apply函数可以提高性能避免线程爆炸以及死锁的情况。
//使用for循环遍历可能导致线程爆炸以及死锁
for (int index = 0; i index < 9999; index +){
dispatch_async(queue, ^{
//执行的操作
});
}
dispatch_barrier_sync(queue, ^{
//执行的操作
});
//使用dispatch_apply函数可管理并发
dispatch_apply(9999, queue, ^(size_t index){
//执行的操作
});
dispatch_apply函数可以快速批量处理数组里的元素。如下:
//假设有个存有大量对象的数组
NSMutableArray *array;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_apply(array.count, queue, ^(size_t index) {
//并列处理数组中的对象
[array objectAtIndex:index];
});
//dispatch_apply执行结束后 异步主队列刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
//主线程刷新UI
});
});
dispatch_suspend/dispatch_resume
想要暂停或者恢复队列的时候,会用到这两个函数。对于已经执行的处理没有影响,暂停队列后,没有执行的处理会停止执行。恢复队列的函数就是使暂停后没有执行的处理能继续执行。
dispatch_suspend(queue); //暂停某个队列的处理
dispatch_resume(queue); //恢复某个队列的处理
Dispatch Semaphore
Dispatch Semaphore是持有计数的信号,可以用来处理并行执行更新数据时,产生数据不一致以及一部分处理需要进行排他控制的问题。计数为0是等待,计数>=1时,把计数减去1不等待。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//生成Dispatch Semaphore 保证可访问数组的线程同时只能有1个
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 10000; ++i) {
dispatch_async(queue, ^{
//等待Dispatch Semaphore的计数值达到>=1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//当Dispatch Semaphore计数值>=1时,再减去1。dispatch_semaphore_wait函数返回。此时计数为0,访问数组的线程只有1个,所以可以安全更新。
[array addObject:[NSNumber numberWithInt:i]];
//dispatch_semaphore_signal函数可以将Dispatch Semaphore的计数+1。(如果有通过dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值增加的线程,就由最先等待的线程执行。)
dispatch_semaphore_signal(semaphore);
});
}
dispatch_once
不管单线程,还是多线程,dispatch_once函数是保证在应用程序执行中只会执行一次处理。dispatch_once可使用在单例、缓存等处理。
/*
第一个参数保证只会执行一次,
第二个参数是要执行的Block
*/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//只会执行一次的操作
});
dispatch_once的主要处理情况如下:
- 线程A执行Block时,任何其它线程都需要等待。
- 线程A执行完Block应该立即标记任务完成状态,然后遍历信号量(Dispatch Semaphore)链来唤醒所有等待线程。
- 线程A遍历信号量链来signal时,任何其他新进入函数的线程都应该直接返回而无需等待。
- 线程A遍历信号量链来signal时,若有其它等待线程B仍在更新或试图更新信号量链,应该保证此线程B能正确完成其任务:a.直接返回 b.等待在信号量上并很快又被唤醒。
- 线程B构造信号量时,应该考虑线程A随时可能改变状态(“等待”、“完成”、“遍历信号量链”)。
- 线程B构造信号量时,应该考虑到另一个线程C也可能正在更新或试图更新信号量链,应该保证B、C都能正常完成其任务:a.增加链节并等待在信号量上 b.发现线程A已经标记“完成”然后直接销毁信号量并退出函数。
Dispatch I/O
读取较大文件时,可以使用多个线程更快的并列读取。Dispatch I/O可以提高文件的读取速度。用到的函数:
- dispatch_io_create 生成Dispatch I/O
- dispatch_io_set_low_water 指定一次读取的大小(分割大小)
- dispatch_io_read 读取文件。
Dispatch Source
Dispatch Source的种类:
![](https://img.haomeiwen.com/i3512027/5723ef369738b6aa.png)
dispatch source函数是一个监视某些类型事件(用户事件、内件事件、计时器等)的对象。当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中。
Dispatch source函数启动时默认状态是挂起的,我们创建完毕之后得主动恢复(调用dispatch_resume恢复函数 ),否则事件不会被传递,也不会被执行。
- 用户事件
有两种:DISPATCH_SOURCE_TYPE_DATA_ADD //变量增加 (事件联结时会把数字相加) DISPATCH_SOURCE_TYPE_DATA_OR //变量OR (事件联结时会把数字进行逻辑运算)
* 内件事件
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建read事件的Dispatch Source
dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,STDIN_FILENO,0,globalQueue);
//指定read事件执行的Block
dispatch_source_set_event_handler(readSource, ^{
char buff[1024];
//从映像中读取
int length = read(STDIN_FILENO, buff, sizeof(buff));
if(length < 0)
{
//错误处理...处理结束取消Dispatch Source
dispatch_source_cancel(readSource);
}
});
//启动Dispatch Source
dispatch_resume(readSource);
* 定时器
//创建DISPATCH_SOURCE_TYPE_TIMER 作为Dispatch Source
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
//指定定时器5秒后执行,允许延迟1s,不指定为重复
dispatch_source_set_timer(timer,dispatch_time(DISPATCH_TIME_NOW, 25ull*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 1ull*NSEC_PER_SEC);
//指定时间内执行Block
dispatch_source_set_event_handler(timer, ^{
NSLog(@" pipe apple 🍎");
dispatch_source_cancel(timer);
});
//取消处理
dispatch_source_set_cancel_handler(timer, ^{
NSLog(@"canceled pipe apple 🍎");
});
//必须主动启动source才会执行
dispatch_resume(timer);
###关于GCD的一些问题
1.dispatch_async 函数如何实现,分发到主队列和全局队列有什么区别,一定会新建线程执行任务么?
__dispatch_async 函数的实现:会把任务添加到队列的一个链表中,添加完后会唤醒队列,根据 vtable (链表)中的函数指针,调用 wakeup 方法。在 wakeup 方法中,从线程池里取出工作线程(如果工作线程是主线程就不用新建,没有就新建线程执行任务),然后在工作线程中取出链表头部指向的 block 并执行 。
分发到主队列的任务由Runloop处理,分发到全局队列的任务由线程池处理。__
2.dispatch_sync 函数如何实现,为什么说 GCD 死锁是队列导致的而不是线程?
__dispatch_sync 函数的实现:不涉及线程池(一般都是在当前线程执行),利用与线程绑定的信号量实现串行。GCD导致死锁:__
//在当前串行队列中执行 dispatch_sync 时,dq_running (表示在运行的任务数量) 为 1
if (slowpath(!dispatch_atomic_cmpxchg2o(dq, dq_running, 0, 1))) {
// _dispatch_barrier_sync_f_slow 函数中使用了线程对应的信号量并且调用 wait 方法,从而 导致线程死锁。
return _dispatch_barrier_sync_f_slow(dq, ctxt, fun);
}
如果换成其他队列提交Block,则进入 _dispatch_barrier_sync_f_invoke函数,它只是保证了 block 执行的原子性,但没有使用线程对应的信号量。
3.信号量是如何实现的,有哪些使用场景?
__主要是用dispatch_semaphore_signal(信号量计数+1,唤醒线程返回非0,否则返回0)和dispatch_semaphore_wait(信号量计数-1)这两个函数,底层调用了内核提供的方法。初始 value 必须大于等于 0,如果为 0 并随后调用 dispatch_semaphore_wait 方法,线程将被阻塞直到别的线程调用了 dispatch_semaphore_signal 方法。__PS:dispatch_semaphore_wait函数返回0表示成功,返回其他则表示超时。
__使用场景:dispatch_group/dispatch_once等.__
4.dispatch_group 的等待与通知、dispatch_once 、dispatch_barrier_async如何实现?
__dispatch_group: 本质就是一个 value 非常大的信号量,等待 group 完成实际上就是等待 value 恢复初始值。而 notify 的作用是将所有注册的回调组装成一个链表,在 dispatch_async 完成时判断 value 是不是恢复初始值,如果是则调用 dispatch_async 异步执行所有注册的回调。__
__dispatch_once 通过一个静态变量来标记 block 是否已被执行,同时使用信号量确保只有一个线程能执行,执行完 block 后会唤醒其他所有等待的线程。__
__dispatch_barrier_async 改变了 block 的 vtable(链表) 标记位,当它将要被取出执行时,会等待前面的 block 都执行完,然后在下一次循环中被执行。__
5.dispatch_source 用来做定时器如何实现,有什么优点和用途?
__实现:source 会被提交到用户指定的队列,然后提交到 manager 队列中,按照触发时间排好序。随后找到最近触发的定时器,调用内核的 select 方法等待。等待结束后,依次唤醒 manager 队列和用户指定队列,最终触发一开始设置的回调 block。
优点:不依赖Runloop,因此任何线程都可以使用。使用了 block块,不会忘记避免循环引用。用处:定时器可以自由控制精度,随时修改间隔时间等。__
6.dispatch_suspend 和 dispatch_resume 如何实现,队列的的暂停和计时器的暂停有区别么?
__dispatch_suspend (将do_suspend_cnt的值+2)和 dispatch_resume 将do_suspend_cnt的值-2)由do_suspend_cnt属性决定,默认值有两个:__
#define DISPATCH_OBJECT_SUSPEND_LOCK 1u (启动)
#define DISPATCH_OBJECT_SUSPEND_INTERVAL 2u(暂停)
__dispatch_source的定时器默认值是暂停的(DISPATCH_OBJECT_SUSPEND_INTERVAL),需要手动开启,而队列的默认值(DISPATCH_OBJECT_SUSPEND_LOCK)默认启动的。__
参考:
https://bestswifter.com/deep-gcd/
Objective-C高级编程 iOS与OS X多线程和内存管理
http://www.jianshu.com/p/c2b14bb999de
网友评论