一、GCD 简介:
全称是 Grand Central Dispatch,是纯 C 语言,提供了非常多强大的函数。
GCD 的优势:
GCD 是苹果公司为多核的并行运算提出的解决方案
GCD 会自动利用更多的 CPU 内核(比如双核、四核)
GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
二、GCD 最根本的三步骤:
将 任务 添加到 队列,并且指定执行任务的 函数。
下面是最基础的写法:
把任务添加到队列 --> 函数
// 1、任务
dispatch_block_t block = ^{
NSLog(@"hello GCD");
}
// 2、串行队列
dispatch_queue_t queue = dispatch_queue_create("com.fanjiduo.cn", NULL);
// 3、异步函数指定要执行的队列,并指定当前要执行的block
dispatch_async(queue, block);
- 任务使用 block 封装
- 任务的 block 没有参数也没有返回值
GCD 有三个关键的名词,分别是函数、队列和任务
1、函数
执行任务的函数有两种
1)、异步
dispatch_async
- 不用等待当前语句执行完毕,就可以执行下一条语句
- 会开启线程执行 block 的任务
- 异步是多线程的代名词
2)、同步
dispatch_sync
- 必须等待当前语句执行完毕,才会执行下一条语句
- 不会开启线程
- 在当前线程执行 block 的任务
2、队列
队列是一种数据结构,支持FIFO(先进先出)
先进去,先调度。
串行队列
并发队列
系统默认提供了两个队列,主队列和 global 全局队列。
主队列是串行队列
dispatch_get_main_queue();
global 是并发队列
dispatch_get_global_queue(0,0);
不建议直接使用全局并发队列 global,因为这是系统提供了,很多地方都会用,最好剥离开来,自己创建。
面试题:下面三段代码打印顺序分别是什么?
代码1
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
答案:代码 1 因为是并发队列,并且都是异步 async 请求,异步不阻塞,但是开辟线程需要耗时,所以 1 之后是 5,2 之后是 4,所以打印顺序为 1、5、2、4、3。
代码2
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
答案:代码 2 因为是并发队列,异步 async 开辟线程需要耗时,而同步sync不开辟线程,会阻塞,所以打印顺序为1、5、2、3、4。
代码3
dispatch_queue_t queue = dispatch_queue_create("fanjiduo", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{//block1
NSLog(@"2");
dispatch_sync(queue, ^{//block2
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
答案:打印1 5 2 之后在下面图中位置闪退:
这是因为造成了 死锁。
为什么会造成死锁,看下图这个队列:
关于死锁再看看下面的代码:
dispatch_sync(dispatch_get_main_queue(), ^(void){
NSLog(@"这里死锁了");
});
执行这个主队列的是主线程,执行了同步函数后,将 block 添加到了主队列中,同时调用 dispatch_syn 这个函数的线程被阻塞(也就是主线程被阻塞),等待 block 执行完成,而执行主线程队列任务的线程正是主线程,此时他处于阻塞状态,所以 block 永远不会被执行,因此主线程一直处于阻塞状态,并非卡在 block 中无法返回,而是根本无法执行到这个 block。
面试题2:
int a = 0;
while (a < 10) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
})
}
NSLog(@"%d", a);
1问:a 为什么会报错?
答:要加 __block
2问:a 输出的值是什么?
答:有多种可能,但肯定是大于等于 10 的,因为 a=10 出循环的时候,可能还有其他线程没有执行完毕,所以最终 a 可能大于 10;
三、GCD 的应用
1、栅栏函数 dispatch_barrier_async
栅栏函数最直接的作用:控制任务执行顺序。
栅栏函数一定要是自定义的并发队列,不能用系统提供的队列。不然没有效果。(系统设计的就是这样,不然阻塞了系统提供的全局并发队列,那就GG了)
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"-------栅栏阻塞----------",[NSThred currentThread]);
});
问:下面的代码有什么问题?为什么?该如何解决?
//dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
dispatch_queue_t concurrentQueue = dispatch_queue_create("fanjiduo", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 2000; i++) {
dispatch_async(concurrentQueue, ^{
NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
NSURL *url = [NSBundle mainBundel] URLForResource:imageName withExtension:nil];
NSData *data = [NSData dataWithContentsOfURL:data];
UIImage *image = [UIImage iamgeWithData:data];
[self.mArray addObject:image];
});
}
答:
问题1:
应该把系统的全局并发队列改为:
dispatch_queue_t concurrentQueue = dispatch_queue_create("fanjiduo", DISPATCH_QUEUE_CONCURRENT);
问题2:
在 addObject 的地方会崩溃,报的错误是 Thread 7:signal SIGABRT
,是线程 BUG。
会产生资源抢夺。可以给 addObject 加锁
@synchronized (self) {
[self.mArray addObject:image];
}
或者加入一个栅栏函数:
dispatch_barrier_async(concurrentQueue, ^{
[self.mArray addObject:image];
});
栅栏函数也可以保证线程安全。
dispatch_barrier_async 与 dispatch_barrier_sync 的区别:
dispatch_barrier_async(concurrentQueue, ^{
});
是只堵塞 concurrentQueue 里的东西,不堵塞其他线程。而
dispatch_barrier_sync(concurrentQueue, ^{
});
是堵塞这行代码之后的所有东西,不管是什么线程。
栅栏函数注意点:
- 一定要是自定义的并发队列
- 必须要求都在同一个队列(缺点:不利于封装,太依赖于必须同一个队列了)
2、调度组
如何避免栅栏函数的缺点,又能实现功能呢?
我们可以使用调度组。
关键函数:
dispatch_group_create
创建组
dispatch_group_async
进组任务
dispatch_group_notify
进组任务执行完毕通知
dispatch_group_wait
进组任务执行等待时间
dispatch_group_enter
进组
dispatch_group_leave
出组
下面是用调度组实现类似上面栅栏函数功能的代码:
//创建调度组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue1 = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
// SIGNAL
dispatch_group_async(group, queue, ^{
NSString *logoStr = @"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3351002169,4211425181&fm=27&gp=0.jpg";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
UIImage *image = [UIImage imageWithData:data];
[self.mArray addObject:image];
});
dispatch_group_async(group, queue1, ^{
NSString *logoStr = @"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3033952616,135704646&fm=27&gp=0.jpg";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
UIImage *image = [UIImage imageWithData:data];
[self.mArray addObject:image];
});
__block UIImage *newImage = nil;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"数组个数:%ld",self.mArray.count);
for (int i = 0; i
从上面的代码可以看到,我们是在系统的“全局并发队列”里进行的请求,最后在“主队列“里进行的更新。这样我们对队列的影响性和需求性相比栅栏函数就降低了。
dispatch_group_t group = dispatch_group_create();
long timeout = dispatch_group_wait(group, 2);
除了上面的写法,还有一种进组和出组的写法:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
// dispatch_group_async -- 下节课的源码分析 --封装意思
dispatch_async(queue, ^{
NSLog(@"第一个走完了");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第二个走完了");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成,可以更新UI");
});
dispatch_group_enter 和 dispatch_group_leave 必须成对出现。
底层原理是:
dispatch_group_enter(group) 进组以后,组就会处于一个休眠状态,内部有一个 signal
信号就会 +1
,dispatch_group_leave 出组 signal
就会 -1
,然后不停的循环判断 signal
是否等于 0
,如果 signal
== 0
了,那么就会调用 group_wakeup
函数,组就会复苏,就会隐性的调用 dispatch_group_notify 通知。
signal
不能为小于 0 的数,否则会崩溃。
dispatch_group_async 和 dispatch_async 的区别是什么?(看libdispatch-913.60.2源码)
3、信号量 dispatch_semaphore_t
GCD 中还有个东西叫做信号量 ** dispatch_semaphore_t**,它可以控制并发数,也可以当成锁来使用。
关键函数:
dispatch_semaphore_create
创建信号量
dispatch_semaphore_wait
信号量等待
dispatch_semaphore_signal
信号量释放
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 信号量 -- gcd控制并发数
// 同步
//总结:由于设定的信号值为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务1");
sleep(1);
NSLog(@"任务1完成");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务2");
sleep(1);
NSLog(@"任务2完成");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务3");
sleep(1);
NSLog(@"任务3完成");
dispatch_semaphore_signal(semaphore);
});
打印结果为:
执行任务2
执行任务1
任务2完成
任务1完成
执行任务3
任务3完成
通过上面的代码可以看出,dispatch_semaphore_create(2),信号量设为的是 2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过 2。所以会等到任务 1 和 2 都执行完才会执行任务 3。
转载请备注原文出处,不得用于商业传播——凡几多