iOS GCD的基本使用
一:什么是GCD
GCD的全程是:Grand Central Dispatch, 直白的用汉语翻译就是:厉害的中枢调度器.
GCD 是iOS 的多线程技术的实现方案,但是它并不是多线程技术,它是“并发解决技术”,是苹果公司研发的,会自动管理线程(这一段定义有点拗口,简单了解就行)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
二:CGD的两个核心概念
1.任务: 执行了什么操作(任务使用block来定义)
2.对列:(是一种数据结构,先进先出)用来存放任务
简而言之,就是 创建任务--创建对列-->把任务放到对列里.
执行任务有两种方式:同步执行(sync)和异步执行(async)。
对列有两种类型:串行对列,并行对列
(代码里还有主队列:主队列 dispatch_get_main_queue(),但是它是一种特殊的串行对列)
(还有全局并发对列 dispatch_get_global_queue(),它也是一种并行对列)
(注意:并行对列并不是说有多个对列并行,不管串行对列还是并行对列,都只有一个对列,对列里放的任务是串行的,叫串行对列,对列里放的任务是并行的,叫并行对列)
队列是一种特殊的线性表,采用 FIFO(先进先出)的原则.放入队列的里任务,先放先执行(按照代码从上到下运行的思想就是,哪个任务先写哪个任务先执行),
注意⚠️:这个“先进先出”的“出”,不是“出结果”的出,而是先“拿出来”执行的出.所以先放进去的任务先执行,但是并不一定先出结果,例如依次放入并行队列的异步任务A和异步任务B,先执行异步任务A,再执行任务B,但是A的逻辑比较耗时,B的结果出来了,A的结果还没出来.
所以理论上任务和对列有4种组合方案:
1.串行对列里添加同步任务;
2.串行对列里添加异步任务;
3.并行对列里添加同步任务; (并行对列里具备了多个任务一起执行的能力,但是由于加入的任务是同步的,所以无法多个任务一同执行,得排队执行)
4.并行对列里添加异步任务.
另外还有一个主对列 dispatch_get_main_queue()
主对列里添加了同步任务
主队列里添加了异步任务
几种组合的区别如下图:
以上图片可以总结为:
同步任务+并发对列:按顺序执行任务,不会开启新线程
异步任务+并发对列:不一定按顺序执行任务,会开启新线程(有几个任务就开启几个线程)
同步任务+串行对列:按顺序执行任务,不会开启新线程
异步任务+串行对列:按顺序执行任务,会开启一个新线程(不管有多少个任务,只有开一个新线程)
记忆点:只要是同步任务,都不会开启新线程;
异步任务会开启新线程,N个异步任务放入到串行对列,只开一个线程,N个异步任务放入并 行对列,会开N个新线程
1.同步任务+并行对列:
//1.同步任务+并行对列
- (void)dispathDemo1
{
dispatch_queue_t queue = dispatch_queue_create(@"同步任务+并行对列", DISPATCH_QUEUE_CONCURRENT);
//在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
NSLog(@"同步任务开始,当前线程==%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
//for循环,创建5个任务,放入queue种
for (int i = 0; i < 5; i ++)
{
//同步函数
dispatch_sync(queue, ^{
if (i == 1) {
[NSThread sleepForTimeInterval:10];
}
NSLog(@"当前打印值==%d,线程==%@",i,[NSThread currentThread]);
});
}
NSLog(@"同步任务结束,当前线程==%@",[NSThread currentThread]);
}
输出的顺序为:
同步任务开始,当前线程==<_NSMainThread: 0x600001704000>{number = 1, name = main}
当前打印值==0,线程==<_NSMainThread: 0x600001704000>{number = 1, name = main}
当前打印值==1,线程==<_NSMainThread: 0x600001704000>{number = 1, name = main}
当前打印值==2,线程==<_NSMainThread: 0x600001704000>{number = 1, name = main}
当前打印值==3,线程==<_NSMainThread: 0x600001704000>{number = 1, name = main}
当前打印值==4,线程==<_NSMainThread: 0x600001704000>{number = 1, name = main}
同步任务结束,当前线程==<_NSMainThread: 0x600001704000>{number = 1, name = main}
可以看到:
所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)
所有任务都在打印的 同步任务开始和syncConcurrent begin—syncConcurrent end之间执行的(同步任务需要等待队列的任务执行结束)
任务按顺序执行的。按顺序执行的原因:虽然并发队列可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。
写累了,不想写了,具体可以参考文章:iOS 多线程GCD_gcd多线程-CSDN博客
https://blog.csdn.net/fengyuyxz/article/details/114835909?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogOpenSearchComplete%7ERate-2-114835909-blog-138373634.235%5Ev43%5Epc_blog_bottom_relevance_base1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogOpenSearchComplete%7ERate-2-114835909-blog-138373634.235%5Ev43%5Epc_blog_bottom_relevance_base1&utm_relevant_index=3
三:CGD执行顺序
例1:
答案在下面代码块里:
- (void)GCDDemo1
{
//并行对列
dispatch_queue_t queue = dispatch_queue_create(@"并行对列", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"11111");
dispatch_async(queue, ^{
NSLog(@"2222");
dispatch_async(queue, ^{
NSLog(@"33333");
});
NSLog(@"4444");
});
NSLog(@"5555");
/*
打印顺序为1、5、2、4、3
1.1先打印,没问题
2.代码运行到 第一个 dispatch_async 异步任务,由于异步任务耗时,且是并行队列,不影响下面的代码(不属于任务里的代码)运行,
所以先打印5
3.进入异步任务dispatch_async,打印2
4.代码运行到第二个 dispatch_async 异步任务,原因与步骤2一样,先打印4
5.最后打印异步任务里的3
*/
}
例2:
- (void)GCDDemo4
{
dispatch_queue_t queue = dispatch_queue_create(@"并发对列", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ //异步,耗时
NSLog(@"11111");
});
dispatch_async(queue, ^{
NSLog(@"2222");
});
dispatch_sync(queue, ^{
NSLog(@"33333");
});
NSLog(@"00000");
dispatch_async(queue, ^{
NSLog(@"77777");
});
dispatch_async(queue, ^{
NSLog(@"88888");
});
dispatch_async(queue, ^{
NSLog(@"99999");
});
/*
A:1230789
B:1237890
C:3120789
D:2137890
答案为A或C
解答:
1.1和2同为异步函数,谁先谁后执行完毕不好说,得看函数本身的耗时情况,所以顺序1、2或者2、1都行
2.同理 789 也是没有三个之间也是没有顺序的,789、879、978等等都行
3.关键点在于 同步函数3,同步函数没走完之前,它后面的函数是不会走的,所以3一定在0的前面.
3.1.同步函数3卡住的是第36行代码,因为是并行对列,结合步骤1,可以得出123执行的顺序也是不一定的,谁的函数耗时少,谁先执行.
4.得出结论-->123 在 0 的面前.
5.123 执行完毕之后 一定是先执行0,因为789都是异步耗时操作
6.所以得出结论123随便排列,然后到0,再到789随便排列.
7.满足以上要求的只有A和C
8.这道题是选择题,如果是填空题,还有好多种可能,例如:2130978、2130897等等(可能是因为答案有多种,才出的选择题)
*/
}
例3:
例子3来看一下不同队列和不同任务的区别:
3.1.并行队列/串行队列 + 同步任务, 执行顺序都是123. 由此可以得出结论,只要有同步任务,就必须先让同步任务走完,才可以走下面的代码,不管你是什么队列
- (void)GCDDemo2
{
dispatch_queue_t concurrentqQueue = dispatch_queue_create(@"并行队列", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"111111");
dispatch_sync(concurrentqQueue, ^{
NSLog(@"222222");
});
NSLog(@"3333");
//打印顺序为 1、2、3
}
//或者
- (void)GCDDemo3
{
dispatch_queue_t serialQueue = dispatch_queue_create(@"串行队列", DISPATCH_QUEUE_SERIAL);
NSLog(@"111111");
dispatch_sync(serialQueue, ^{
NSLog(@"222222");
});
NSLog(@"3333");
//打印顺序为 1、2、3
}
3.1.并行队列+ 异步任务, 执行顺序都是132.
- (void)GCDDemo2
{
dispatch_queue_t concurrentqQueue = dispatch_queue_create(@"并行队列", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"111111");
dispatch_async(concurrentqQueue, ^{
NSLog(@"222222");
});
NSLog(@"3333");
//打印顺序为 1、3、2. 并行队列,可以多个任务一起执行,异步任务耗时,所以2没走完,先走3
}
3.2.串行队列+ 异步任务, 执行顺序都是132.
- (void)GCDDemo2
{
dispatch_queue_t serialQueue = dispatch_queue_create(@"串行队列", DISPATCH_QUEUE_SERIAL);
NSLog(@"111111");
dispatch_async(serialQueue, ^{
NSLog(@"222222");
});
NSLog(@"3333");
//打印顺序为1、3、2
//串行对列,理论上是1、2、3.按顺序打印,但是因为2是异步任务,耗时操作且不会阻塞线程,所以先打印3.然后再打印2. (2是执行了的,但是由于是耗时,所以没有执行完毕,没有打印,串行队列是按顺序执行的,这里望周知,执行顺序是1、2、3,但是打印顺序是1、3、2)
}
例4:
特例: 放入主队列就会崩溃,目前不知道啥原因. 按理说主队列也是串行对列.不应该崩溃,先留个笔记,稍后研究解答:
(PS:同步任务不能跟主线程一起使用,这两个搭配会造成死锁、崩溃)
- (void)GCDDemo2
{
//dispatch_get_main_queue() 主队列,崩溃
NSLog(@"111111");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"222222");
//代码在这里崩溃了,可能是死锁,
});
NSLog(@"3333");
//打印1之后,就crash了
}