一、简单问答环节
1.什么是GCD
答:一种异步执行任务的技术
2.什么是线程
答:源代码通过编译器转换为CPU命令列,即二进制代码,一个CPU执行这些二进制代码的形式为一行一行执行,执行顺序是线性的(永远不会出现分叉),即为线程
3.什么是多线程
答:一个CPU执行多条这种线性的无分叉路径,即为多线程
4.一个CPU如何实现多线程
答:一个CPU同时只能做一件事,当CPU在多个线程间频繁的切换,由于速度很快,给人的感觉就是同时执行多个线程
5.多线程会产生哪些问题
答:1.更新相同的资源,导致数据不一致(数据竞争)
2.多个线程之间相互等待,谁也不往下执行(死锁)
3.多线程对内存消耗很大
还有很多问题,不一一列举
二、结合GCD的API讲解
1.Dispatch Queue(调度队列)
a.队列的种类
队列可以通俗的理解为坐火车检票,乘客排着队准备通过检票口,而每一个乘客相当于一个要执行的代码块(任务)
队列总体分为两种类型 Serial Dispatch Queue 和 Concurrent Dispatch Queue,可以通俗的理解为一个检票口,和多个检票口(即串行队列和并行队列)
Serial Dispatch Queue 类型的队列每次只允许一个乘客通过,后面的乘客想通过检票口,必须要等前面的乘客先通过(每次只能执行一个代码块)
Concurrent Dispatch Queue 类型的队列相当于有多个检票口,可以同时允许很多乘客检票(即同时执行很多个代码块)
b.创建一个队列
队列的类型是dispatch_queue_t创建一个队列用dispatch_queue_create方法
// 创建一个Serial类型的队列
dispatch_queue_t serial_queue = dispatch_queue_create("com.ms.serial", NULL);
// 创建一个concurrent类型的队列
dispatch_queue_t concurrent_queue = dispatch_queue_create("com.ms.concurrent", DISPATCH_QUEUE_CONCURRENT);
第一个参数是队列的名字,一般用程序ID逆序全域名的形式,这个参数在程序报错时候的返回信息中会体现,还是很有用的
第二个参数为生成指定类型队列的参数
注意
- 每生成一个Serial类型的队列相当于生成并使用一个线程,所以不宜生成过多的Serial类型队列,这样内存会开销很大
- 一个线程对数据来说是安全的,所以可以针对一个数据生成一个Serial类型的队列,例如更新一个文件,但是要避免针对一个文件生成多个Serial类型的队列,因为多线程的关系,会产生数据竞争
- 当想要对一个数据进行并发操作,又要避免数据竞争问题的时候,用Concurrent类型的队列,系统会有效的管理这些线程,避免出现问题
c.系统自带的队列
GCD的API为我们准备了两个队列Main Dispatch Queue 和 Global Dispatch Queue分别对应Serial类型的队列和Concurrent类型的队列
// 获取Main Dispatch Queue
dispatch_queue_t main_queue = dispatch_get_main_queue();
// 获取Global Dispatch Queue
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Global Dispatch Queue有四种执行优先级,高优先级相当于会员的作用,可以让你插队,对应dispatch_get_global_queue方法的第一个参数,分别是
参数 | 优先级 |
---|---|
DISPATCH_QUEUE_PRIORITY_HIGH |
高优先级 |
DISPATCH_QUEUE_PRIORITY_DEFAULT |
默认优先级 |
DISPATCH_QUEUE_PRIORITY_LOW |
低优先级 |
DISPATCH_QUEUE_PRIORITY_BACKGROUND |
后台优先级 |
需要注意,优先级只是大致的判断,并不是绝对的
d.修改队列优先级
用dispatch_queue_create方法创建的队列优先级与Global Dispatch Queue的默认优先级相同,即DISPATCH_QUEUE_PRIORITY_DEFAULT
想变更生成的队列的优先级的时候,用方法dispatch_set_target_queue
例子是将创建的concurrent_queue的优先级设置成DISPATCH_QUEUE_PRIORITY_BACKGROUND优先级
// 创建的队列,优先级是DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_queue_t concurrent_queue = dispatch_queue_create("com.ms.concurrent", DISPATCH_QUEUE_CONCURRENT);
// 获取的Global Dispatch Queue对列,注意优先级是 DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// 修改concurrent_queue的优先级
dispatch_set_target_queue(concurrent_queue, global_queue);
注意
- 不可以修改Main Dispatch Queue 和 Global Dispatch Queue的优先级
dispatch_set_target_queue的另一个作用是可以决定队列的执行层次,防止多个队列并行执行
假设有五个Serial Dispatch Queue,那么这五个Serial Dispatch Queue是并行执行的,如果将其中四个Serial Dispatch Queue设置为其中某一个Serial Dispatch Queue的优先级,则不会出现并行执行的情况
2.添加任务到队列
前面已经讲解了如何创建一个队列,下面介绍如何将任务添加到队列中
将任务添加到队列分为同步(sync)和异步(async)
举个例子,你有车了,为了嘚瑟有车,要开车去送你的朋友去火车站坐火车,但是你可能有好几个朋友要去坐火车,但是这几个朋友之间相互不认识,分别在不同的地方,要分别送他们
同步(sync)就是你送一个朋友去火车站之后,必须确认你朋友经过检票口上了火车之后你才回去接其他的朋友,也不管其他的朋友是否着急
异步(async)就是你送去一个朋友之后,也没管它是否检票上车,就返回去接其他的朋友了
a. dispatch_sync()
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
将任务同步添加到队列中
例子是将打印1的任务同步添加到serial_queue队列中
dispatch_queue_t serial_queue = dispatch_queue_create("com.ms.serial", NULL);
dispatch_sync(serial_queue, ^{
// 要做的任务
NSLog(@"1");
});
- 第一个参数是要添加的队列
- 第二个参数是block,就是我们要添加的任务
b. dispatch_sync_f()
void dispatch_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
作用和dispatch_sync()方法一样,不同点是这个方法可以将函数提交到队列中
例子中是将一个函数print1()添加到队列中,实现的也是打印1的任务
void print1(void *param){
printf("%s", param);
}
char *str = "1";
dispatch_sync_f(serial_queue, str, print1);
- 第一个参数是要添加的队列
- 第二个参数是地址,这个参数是第三个参数(一个函数)要用的参数
- 第三个参数是一个
void (*dispatch_function_t)(void *_Nullable)
类型的函数
c. dispatch_async()和dispatch_async_f()
类比上面的a和b,就是同步和异步的区别
将任务添加到队列后,系统会对队列进行retain,当任务完成后队列被release
注意
- dispatch_sync()和dispatch_async()中第二个参数是block,这个block中即使使用self也不会出现循环引用的情况,可以放心使用
- dispatch_sync()别乱用,在串行队列中用dispatch_sync()将任务添加到当前串行队列,会发生死锁,因为block的执行需要等待前面的任务,也就是dispatch_sync执行完成,两者互相等待,在并行队列中就不会出现这种情况
- 前面介绍过Main Dispatch Queue是串行队列,你懂的😏