GCD 简介
https://developer.apple.com/documentation/dispatch?language=objc
Dispatch(调度)
Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system.(多核硬件上执行代码同时通过提交工作调度队列管理的系统。)
Overview
Dispatch, also known as Grand Central Dispatch (GCD), contains language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in macOS, iOS, watchOS, and tvOS.(Dispatch, also known as Grand Central Dispatch (GCD), contains language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in macOS, iOS, watchOS, and tvOS.)
The BSD subsystem, Core Foundation, and Cocoa APIs have all been extended to use these enhancements to help both the system and your application to run faster, more efficiently, and with improved responsiveness. Consider how difficult it is for a single application to use multiple cores effectively, let alone to do it on different computers with different numbers of computing cores or in an environment with multiple applications competing for those cores. GCD, operating at the system level, can better accommodate the needs of all running applications, matching them to the available system resources in a balanced fashion.(BSD子系统,Core Foundation,和Cocoa APIs都扩展到使用这些增强功能,帮助系统和应用程序运行得更快,更有效,提高响应能力。认为是多么困难的一个应用程序有效地使用多核,更别说去做不同的计算机使用不同数量的计算核心或与多个应用程序的环境中竞争的核心。GCD、操作在系统层面上,可以更好地适应所有正在运行的应用程序的需要,匹配他们可用的系统资源平衡的方式。)
GCD是异步执行任务的技术之一。一般将应用程序
因为使用 GCD 有很多好处,具体如下:
- GCD 可用于多核的并行运算;
- GCD 会自动利用更多的 CPU 内核(比如双核、四核);
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
GCD 任务和队列
学习 GCD 之前,先来了解 GCD 中两个核心概念:『任务』 和 『队列』。
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
- 同步执行(sync):
同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。 - 异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
注意:异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关(下面会讲)。
队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。队列的结构可参考下图:
在 GCD 中有两种队列:『串行队列』 和 『并发队列』。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
- 串行队列(Serial Dispatch Queue):
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务) - 并发队列(Concurrent Dispatch Queue):
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。
两者具体区别如下两图所示:
GCD 的使用步骤
GCD 的使用步骤其实很简单,只有两步:
创建一个队列(串行队列或并发队列);
将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。
下边来看看队列的创建方法 / 获取方法,以及任务的创建方法。
虽然使用 GCD 只需两步,但是既然我们有两种队列(串行队列 / 并发队列),两种任务执行方式(同步执行 / 异步执行),那么我们就有了四种不同的组合方式。这四种不同的组合方式是:
- 同步执行 + 并发队列
- 异步执行 + 并发队列
- 同步执行 + 串行队列
- 异步执行 + 串行队列
实际上,刚才还说了两种特殊队列:全局并发队列、主队列。全局并发队列可以作为普通并发队列来使用。但是主队列因为有点特殊,所以我们就又多了两种组合方式。这样就有六种不同的组合方式了。
- 同步执行 + 主队列
- 异步执行 + 主队列
那么这几种不同组合方式各有什么区别呢?
这里我们先上结论,后面再来详细讲解。你可以直接查看 3.3 任务和队列不同组合方式的区别 中的表格结果,然后跳过 4. GCD的基本使用 继续往后看。
任务和队列不同组合方式的区别
我们先来考虑最基本的使用,也就是当前线程为 『主线程』 的环境下,『不同队列』+『不同任务』 简单组合使用的不同区别。暂时不考虑 『队列中嵌套队列』 的这种复杂情况。
『主线程』中,『不同队列』+『不同任务』简单组合的区别:
区别 并发队列 串行队列 主队列
区别 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 死锁卡住不执行 |
异步(async) | 有开启新线程,并发执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
注意:从上边可看出: 『主线程』 中调用 『主队列』+『同步执行』 会导致死锁问题。
这是因为 主队列中追加的同步任务 和 主线程本身的任务 两者之间相互等待,阻塞了 『主队列』,最终造成了主队列所在的线程(主线程)死锁问题。
而如果我们在 『其他线程』 调用 『主队列』+『同步执行』,则不会阻塞 『主队列』,自然也不会造成死锁问题。最终的结果是:不会开启新线程,串行执行任务。
队列嵌套情况下,不同组合方式区别
除了上边提到的『主线程』中调用『主队列』+『同步执行』会导致死锁问题。实际在使用『串行队列』的时候,也可能出现阻塞『串行队列』所在线程的情况发生,从而造成死锁问题。这种情况多见于同一个串行队列的嵌套使用。
比如下面代码这样:在『异步执行』+『串行队列』的任务中,又嵌套了『当前的串行队列』,然后进行『同步执行』。
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 异步执行 + 串行队列
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
});
执行上面的代码会导致 串行队列中追加的任务 和 串行队列中原有的任务 两者之间相互等待,阻塞了『串行队列』,最终造成了串行队列所在的线程(子线程)死锁问题。
关于 『队列中嵌套队列』这种复杂情况,这里也简单做一个总结。不过这里只考虑同一个队列的嵌套情况,关于多个队列的相互嵌套情况还请自行研究,或者等我最新的文章发布。
『不同队列』+『不同任务』 组合,以及 『队列中嵌套队列』 使用的区别:
区别 | 『异步执行+并发队列』嵌套『同一个并发队列』 | 『同步执行+并发队列』嵌套『同一个并发队列』 | 『异步执行+串行队列』嵌套『同一个串行队列』 | 『同步执行+串行队列』嵌套『同一个串行队列』 |
---|---|---|---|---|
同步(sync) | 没有开启新的线程,串行执行任务 | 没有开启新线程,串行执行任务 | 死锁卡住不执行 | 死锁卡住不执行 |
异步(async) | 有开启新线程,并发执行任务 | 有开启新线程,并发执行任务 | 有开启新线程(1 条),串行执行任务 | 有开启新线程(1 条),串行执行任务 |
#pragma mark --串行并行
//同步串行
- (void)test00 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.Charles.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_sync(serialQueue, ^{
NSLog(@"2");//主线程
});
NSLog(@"3");
//123并且都在主线程无意义
}
//同步并行
- (void)test01 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.Charles.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(concurrentQueue, ^{
NSLog(@"2");//主线程
});
NSLog(@"3");
//123并且都在主线程无意义
}
//异步串行
- (void)test02 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.Charles.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
});
NSLog(@"3");
//123或者132
}
//异步并行
- (void)test03 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.Charles.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(concurrentQueue, ^{
NSLog(@"2");
});
NSLog(@"3");
//123或者132
}
// 串行同步
- (void)test1 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.Charles.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"1");
});
NSLog(@"2");
dispatch_sync(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
//打印顺序1、2、3、4
}
// 串行异步
- (void)test2 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.Charles.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"1");
});
NSLog(@"2");
dispatch_async(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
//1234 ,2134,1243,2413,2143
//1 一定在 3 之前打印出来,2 一定在 4 之前打印,2 一定在 3 之前打印
}
// 串行异步中嵌套异步
- (void)test3 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.Charles.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");//子线程
dispatch_async(serialQueue, ^{
NSLog(@"3");//子线程
});
NSLog(@"4");
});
NSLog(@"5");
//1在前,后面乱序
}
// 串行异步中嵌套同步
- (void)test4 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.Charles.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
dispatch_sync(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
//打印顺序152或者125然后死锁
}
// 串行同步中嵌套异步
- (void)test5 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.Charles.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_sync(serialQueue, ^{
NSLog(@"2");
dispatch_async(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
//12345,12435,12453。这里1一定在最前,2 一定在 4 前,4 一定在 5 前
}
// 串行同步中嵌套同步
- (void)test6 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.Charles.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_sync(serialQueue, ^{
NSLog(@"2");
dispatch_sync(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
//12 然后死锁
}
// 并发同步
- (void)test7 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.Charles.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
NSLog(@"1");
});
NSLog(@"2");
dispatch_sync(concurrentQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
//打印顺序1、2、3、4
}
// 并发异步
- (void)test8 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.Charles.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"1");
});
NSLog(@"2");
dispatch_async(concurrentQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
//这里只能保证 24 顺序执行,13 乱序,可能插在任意位置:2413 ,2431,2143,2341,2134,2314。
}
// 并发异步中嵌套同步
- (void)test9 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.Charles.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(concurrentQueue, ^{
NSLog(@"2");
dispatch_sync(concurrentQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
//这里会打印出 4 个结果:12345,12534,12354,15234。注意同步操作保证了 3 一定会在 4 之前打印出来
}
// 并发同步中嵌套异步
- (void)test10 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.Charles.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(concurrentQueue, ^{
NSLog(@"2");
dispatch_async(concurrentQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
//这里会打印出 3 个结果:12345,12435,12453。这里同步操作保证了 2 和 4 一定在 3 和 5 之前打印出来
}
// 并发同步中嵌同步
- (void)test11 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.Charles.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(concurrentQueue, ^{
NSLog(@"2");
dispatch_sync(concurrentQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
//12345 不开辟子线程
}
全局队列和主队列
#pragma mark --globle_queue main_queue
//异步主队列
- (void)fun0 {
NSLog(@"1");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2");//主线程
});
NSLog(@"3");
//1、3、2
}
//异步全局队列
- (void)fun1 {
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2");
});
NSLog(@"3");
//123或者132
}
//同步主队列
- (void)fun2 {
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
//1 之后死锁
}
//同步全局队列
- (void)fun3 {
NSLog(@"1");
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2");
NSLog(@"%@", [NSThread currentThread]);//在主线程
});
NSLog(@"3");
//123没用
}
//异步全局队列嵌套同步主队列
- (void)fun4 {
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
//15234或者12534
}
GCD 线程间的通信
在 iOS 开发过程中,我们一般在主线程里边进行 UI 刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
GCD 的其他方法
GCD 延时执行方法:dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//
});
GCD 一次性代码(只执行一次):dispatch_once
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
GCD 快速迭代方法:dispatch_apply
- 通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。
/**
* 快速迭代方法 dispatch_apply
*/
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
输出结果:
2019-08-08 15:05:04.715266+0800 YSC-GCD-demo[17771:4285619] apply—-begin
2019-08-08 15:05:04.715492+0800 YSC-GCD-demo[17771:4285619] 0—-{number = 1, name = main}
2019-08-08 15:05:04.715516+0800 YSC-GCD-demo[17771:4285722] 1—-{number = 3, name = (null)}
2019-08-08 15:05:04.715526+0800 YSC-GCD-demo[17771:4285720] 3—-{number = 5, name = (null)}
2019-08-08 15:05:04.715564+0800 YSC-GCD-demo[17771:4285721] 2—-{number = 7, name = (null)}
2019-08-08 15:05:04.715555+0800 YSC-GCD-demo[17771:4285719] 4—-{number = 6, name = (null)}
2019-08-08 15:05:04.715578+0800 YSC-GCD-demo[17771:4285728] 5—-{number = 4, name = (null)}
2019-08-08 15:05:04.715677+0800 YSC-GCD-demo[17771:4285619] apply—-end
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是 apply---end 一定在最后执行。这是因为 dispatch_apply 方法会等待全部任务执行完毕。
dispatch_barrier_async
- (void)barrier {
// dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
// 作用:
// 1.在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。
// 2.避免数据竞争
// 1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 2.向队列中添加任务
dispatch_async(queue, ^{ // 1.2是并行的
NSLog(@"任务1, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2, %@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"任务 barrier, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{ // 这两个是同时执行的
NSLog(@"任务3, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务4, %@",[NSThread currentThread]);
});
}
GCD 队列组:dispatch_group
有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
- 调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合来实现 dispatch_group_async。
- 调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。
- (void)group {
/**创建自己的队列*/
dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next", DISPATCH_QUEUE_CONCURRENT);
/**创建一个队列组*/
dispatch_group_t dispatchGroup = dispatch_group_create();
/**将队列任务添加到队列组中*/
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"dispatch-1");
}];
[task resume];
});
/**将队列任务添加到队列组中*/
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"dispatch-2");
}];
[task resume];
});
/**队列组完成调用函数*/
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"end");
});
}
dispatch_group_enter、dispatch_group_leave
- dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
- dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
- 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。
- (void)groupCallback {
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
dispatch_group_async(downloadGroup, dispatch_get_global_queue(0, 0), ^{
dispatch_group_enter(downloadGroup);
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"线程:%@---%d---%d",[NSThread currentThread], i, i);
dispatch_group_leave(downloadGroup);
}];
[task resume];
});
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSLog(@"dispatch_group_notify");
});
}
GCD 信号量:dispatch_semaphore
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,可通过。
Dispatch Semaphore 提供了三个方法:
- dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
- dispatch_semaphore_signal:发送一个信号,让信号总量加 1
- dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。
Dispatch Semaphore 在实际开发中主要用于:
- 限制最大并发数
- 保证线程安全,为线程加锁
- 保持线程同步,将异步执行任务转换为同步执行任务
//限制最大并发
- (void)semaphore0 {
// 创建信号量,参数:信号量的初值,如果小于0则会返回NULL
// dispatch_semaphore_create(信号量值)
// 等待降低信号量
// dispatch_semaphore_wait(信号量,等待时间)
// 提高信号量
// dispatch_semaphore_signal(信号量)
// crate的value表示,最多几个资源可访问
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
}
//线程安全锁
- (void)semaphore1 {
dispatch_semaphore_t semaphoreLock = dispatch_semaphore_create(1);
for (NSInteger i = 0; i < 50; i ++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
NSLog(@"i: %ld", i);
dispatch_semaphore_signal(semaphoreLock);
});
}
}
//多个异步任务完成后处理回调任务,不阻塞主线程,(类似于groupCallback)
- (void)semaphore4 {
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
//代表http访问返回的数量
//这里模仿的http请求block块都是在同一线程(主线程)执行返回的,所以对这个变量的访问不存在资源竞争问题,故不需要枷锁处理
//如果网络请求在不同线程返回,要对这个变量进行枷锁处理,不然很会有资源竞争危险
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//demo testUsingSemaphore方法是在主线程调用的,不直接调用遍历执行,而是嵌套了一个异步,是为了避免主线程阻塞
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
sleep(arc4random_uniform(2));
NSLog(@"%d---%@",i,[NSThread currentThread]);
//全部请求返回才触发signal
if (i == 9) {
dispatch_semaphore_signal(sem);
}
}];
[task resume];
}
//如果全部请求没有返回则该线程会一直阻塞
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"all http request done! end thread: %@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"UI update in main thread!");
});
});
NSLog(@"主线程任务");
}
部分内容参考:
iOS 多线程:『GCD』详尽总结
Demo地址:GCD