本文主要是介绍并发编程 - GCD的任务和队列,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
引言
在并发编程的世界中,我们可以从开发中最常用的 Grand Central Dispatch (GCD) 开始着手。相比于其他多线程方案,GCD 具备诸多优势:
- 支持多核并行计算
- 自动利用多核 CPU 的资源
- 自动管理线程的生命周期
- 提供两个可直接使用的队列(主队列和全局队列)
- 允许我们创建自定义队列
在 GCD 中,最核心的两个概念便是任务和队列。
任务
任务就是执行操作的意思也就是我们在线程中执行的那一段代码,在GCD中就是放在block里面的代码。
而执行任务有两种方式:
同步执行(sync):
- 同步添加任务到指定的队列中,任务会顺序执行,在上一个任务结束之前,当前任务会一直等待,直到队列里面的任务完成后再继续执行。
- 只能在当前的线程中执行任务,不具备开启新线程的能力。
异步执行(async):
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
- 可以在新的现场中执行任务,具备开启新线程的能力。
这两者的主要区别在于:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
简单来讲,同步同时只能做一件事儿,而异步可以同时做多件事儿。
但是有一个非常需要注意的一点:异步执行(async)虽然具备开启新线程的能力,但是并不一定就会开启新的线程,这还和任务指定的队列类型有关。
队列
队列指的是执行任务的队列,队列是一种特殊的线性表,采用FIFO(先进先出)的原则,新的任务总是被插入到队列的末尾。而任务在执行时总是从队列的最头部开始读取执行。
队列的概念大家应该并不陌生,队列先进先出,栈先进后出,在这里就不过多介绍了。
在GCD中队列也是有两种:
串行队列(Serial Dispatch Queue):
- 在串行队列中每次只能有一个任务被执行,队列中的所有任务都需要按顺序执行,在串行队列中可能会开启新的线程。
并发队列(Concurrent Dispatch Queue):
- 在并发队列中任务可以同时执行,可以开启多个线程同时执行任务。
两者的主要区别在于队列中的任务执行顺序不同。
同样值得注意的是并发队列的并发功能只有在执行异步任务时才有效。
关于队列和任务的组合我们先给出两个图片直观的感受一下,然后我们再来详细的讨论一下它们的使用场景。
使用任务和队列
GCD之所以在并发编程中比较受欢迎的原因就是使用起来很容易,只需要两个步骤:
- 创建队列
- 将任务添加到队列
创建队列
使用DispatchQueue提供的初始化方法直接创建串行队列和并发队列。
//1.创建一个串行队列let queue = DispatchQueue(label: "com.louis.GCDTestManager")//2.创建一个并发队列let queue1 = DispatchQueue(label: "com.louis.GCDTestManager1", qos: .default, attributes: .concurrent)
另外GCD还提供了两个特殊的队列:主队列(Main Queue)和全局队列(Global Queue)。
//3.主队列let mainQueue = DispatchQueue.main//4.全局队列let globalQueue = DispatchQueue.global()
主队列属于串行队列,与主线程关联。全局队列属于并发队列,由系统提供。
创建任务
有了队列之后我们就可以在指定的队列中执行任务。
//1.同步执行任务queue.sync {for i in 0...10 {print("同步执行任务1:\(i)")}}//2.异步执行任务queue.async {for i in 0...10 {print("异步执行任务1:\(i)")}}
在上面的代码中我们都采用了串行队列来进行同步和异步执行任务。
队列和任务组合
不管是队列还是任务的创建都非常容易,但是当我们使用它们时并没有这么简单,我们需要根据场景来判断队列和任务该如何组合,那我们就需要知道每一个组合的结果。
我们先来讨论比较简单的串行队列。
串行队列+同步任务
任务将会在当前线程中执行,不会开启新的线程。任务会按顺序一个一个执行。
代码如下:
/// 同步任务 + 串行队列func syncSerial() {// 当前线程print("当前线程:\(Thread.current)")// 开始print("syncSerial 开始")//1.创建一个串行队列let queue = DispatchQueue(label: "com.louis.GCDTestManager")//2.同步执行任务1queue.sync {for i in 0...10 {print("同步任务1:\(i)" + "---当前线程:\(Thread.current)")}}//3.同步执行任务2queue.sync {for i in 0...10 {print("同步任务2:\(i)" + "---当前线程:\(Thread.current)")}}print("syncSerial 结束")}
结果如下:
当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
syncSerial 开始
同步任务1:0---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:1---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:2---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:3---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:4---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:5---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:6---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:7---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:8---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:9---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务1:10---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:0---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:1---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:2---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:3---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:4---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:5---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:6---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:7---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:8---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:9---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
同步任务2:10---当前线程:<_NSMainThread: 0x301cac000>{number = 1, name = main}
syncSerial 结束
- 可以发现所有的任务当前线程中执行,没有开启新的线程。
- 所有任务都在“syncSerial 开始”和“syncSerial 结束”之间。
- 所有任务都是按顺序执行的。
串行队列+异步任务
可能会开启新的线程,但由于是串行队列任务仍然会按顺序执行,一个任务执行完再执行另一个任务。
代码如下:
/// 异步任务 + 串行队列func asyncSerial() {// 当前线程print("当前线程:\(Thread.current)")// 开始print("asyncSerial 开始")//1.创建一个串行队列let queue = DispatchQueue(label: "com.louis.GCDTestManager")//2.异步执行任务1queue.async {for i in 0...10 {print("异步任务1:\(i)" + "---当前线程:\(Thread.current)")}}//3.异步执行任务2queue.async {for i in 0...10 {print("异步任务2:\(i)" + "---当前线程:\(Thread.current)")}}print("asyncSerial 结束")}
结果如下:
当前线程:<_NSMainThread: 0x3000bc040>{number = 1, name = main}
asyncSerial 开始
asyncSerial 结束
异步任务1:0---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:1---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:2---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:3---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:4---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:5---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:6---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:7---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:8---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:9---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务1:10---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:0---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:1---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:2---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:3---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:4---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:5---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:6---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:7---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:8---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:9---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
异步任务2:10---当前线程:<NSThread: 0x3000e8340>{number = 3, name = (null)}
- 开启了新的线程。
- 所有任务仍然是顺序执行的。
- 所有任务都是在asyncSerial 结束之后执行的,说明异步不会阻塞当前线程。
并发队列+同步任务
任务在当前线程中执行,不会开启新的线程,任务按顺序执行。
代码如下:
/// 同步任务 + 并发队列func syncConcurrent() {// 当前线程print("当前线程:\(Thread.current)")// 开始print("syncConcurrent 开始")//1.创建一个并发队列let queue = DispatchQueue(label: "com.louis.GCDTestManager", attributes: .concurrent)//2.同步执行任务1queue.sync {for i in 0...10 {print("同步任务1:\(i)" + "---当前线程:\(Thread.current)")}}//3.同步执行任务2queue.sync {for i in 0...10 {print("同步任务2:\(i)" + "---当前线程:\(Thread.current)")}}print("syncConcurrent 结束")}
结果如下:
当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
syncConcurrent 开始
同步任务1:0---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:1---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:2---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:3---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:4---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:5---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:6---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:7---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:8---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:9---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务1:10---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:0---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:1---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:2---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:3---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:4---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:5---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:6---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:7---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:8---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:9---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
同步任务2:10---当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
syncConcurrent 结束
- 所有任务都在当前线程执行。
- 所有任务都在“syncConcurrent 开始”和“syncConcurrent 结束”之间执行。
- 任务顺序执行,因为都是同步任务,所以即使在并发队列中它也不具备开启新线程的能力。
并发队列+异步任务
可以开启多个线程,任务同时执行,没有固定顺序。
代码如下:
/// 异步任务 + 并发队列func asyncConcurrent() {// 当前线程print("当前线程:\(Thread.current)")// 开始print("asyncConcurrent 开始")//1.创建一个并发队列let queue = DispatchQueue(label: "com.louis.GCDTestManager", attributes: .concurrent)//2.异步执行任务1queue.async {for i in 0...10 {print("异步任务1:\(i)" + "---当前线程:\(Thread.current)")}}//3.异步执行任务2queue.async {for i in 0...10 {print("异步任务2:\(i)" + "---当前线程:\(Thread.current)")}}print("asyncConcurrent 结束")}
结果如下:
当前线程:<_NSMainThread: 0x3009f8100>{number = 1, name = main}
asyncConcurrent 开始
asyncConcurrent 结束
异步任务1:0---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务1:1---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务1:2---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务1:3---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务1:4---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务2:0---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务2:1---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务2:2---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务1:5---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务1:6---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务1:7---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务2:3---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务1:8---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务2:4---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务1:9---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务2:5---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务1:10---当前线程:<NSThread: 0x3009ac200>{number = 9, name = (null)}
异步任务2:6---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务2:7---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务2:8---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务2:9---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
异步任务2:10---当前线程:<NSThread: 0x3009a8000>{number = 10, name = (null)}
- 开了多个线程。
- 任务执行没有固定顺序。
- 所有任务都在“asyncConcurrent 结束”之后执行,说明没有阻塞线程。
主队列
分析完了所有任务和队列的组合和结构,我们还需要格外注意一下主队列,主队列属于串行队列,但由于我们的代码默认在主队列上执行所以在进行组合时会有些不一样的效果。
主队列+同步任务
在主队列中同步执行任务会直接卡死崩溃。
代码如下:
/// 同步任务 + 主队列func syncMain() {// 当前线程print("当前线程:\(Thread.current)")// 开始print("syncMain 开始")//1.获取主队列let queue = DispatchQueue.main//2.同步执行任务1queue.sync {for i in 0...10 {print("同步任务1:\(i)" + "---当前线程:\(Thread.current)")}}//3.同步执行任务2queue.sync {for i in 0...10 {print("同步任务2:\(i)" + "---当前线程:\(Thread.current)")}}print("syncMain 结束")}
结果直接崩溃:
因为当我们把同步任务1添加到主队列时,它会等待当前队列中的任务执行完成再来执行任务1。
但是由于我们把任务1添加到了主队列,主队列又需要等待任务1执行完成才能继续执行。
这样就造成了相互等待,所以就卡死了。
但是如果我们并不是在主线程调用这个方法,那么就不会出现问题。
修改代码如下:
/// 同步任务 + 主队列func syncMain() {let thread = Thread(target: self, selector: #selector(startSyncMain), object: nil)thread.start()}@objc func startSyncMain() {// 当前线程print("当前线程:\(Thread.current)")// 开始print("syncMain 开始")//1.获取主队列let queue = DispatchQueue.main//2.同步执行任务1queue.sync {for i in 0...10 {print("同步任务1:\(i)" + "---当前线程:\(Thread.current)")}}//3.同步执行任务2queue.sync {for i in 0...10 {print("同步任务2:\(i)" + "---当前线程:\(Thread.current)")}}print("syncMain 结束")}
结果如下:
当前线程:<NSThread: 0x301d200c0>{number = 7, name = (null)}
syncMain 开始
同步任务1:0---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:1---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:2---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:3---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:4---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:5---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:6---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:7---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:8---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:9---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务1:10---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:0---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:1---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:2---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:3---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:4---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:5---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:6---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:7---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:8---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:9---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
同步任务2:10---当前线程:<_NSMainThread: 0x301d6c100>{number = 1, name = main}
syncMain 结束
- 所有任务都在主线程中执行。
- 所有任务顺序执行。
- 所有任务都在“syncMain 开始”和“syncMain 结束”之间执行。
主队列+异步任务
不会开启新的线程,任务顺序执行。
代码如下:
/// 异步任务 + 主队列func asyncMain() {// 当前线程print("当前线程:\(Thread.current)")// 开始print("asyncMain 开始")//1.获取主队列let queue = DispatchQueue.main//2.异步执行任务1queue.async {for i in 0...10 {print("异步任务1:\(i)" + "---当前线程:\(Thread.current)")}}//3.异步执行任务2queue.async {for i in 0...10 {print("异步任务2:\(i)" + "---当前线程:\(Thread.current)")}}print("asyncMain 结束")}
结果如下:
当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
asyncMain 开始
asyncMain 结束
异步任务1:0---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:1---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:2---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:3---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:4---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:5---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:6---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:7---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:8---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:9---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务1:10---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:0---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:1---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:2---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:3---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:4---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:5---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:6---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:7---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:8---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:9---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
异步任务2:10---当前线程:<_NSMainThread: 0x3028c4100>{number = 1, name = main}
- 所有任务都在主线程中执行,没有开启新的线程。
- 任务都是在“asyncMain 结束”之后执行,没有阻塞当前线程。
- 任务按顺序执行,因为主队列时串行队列。
结语
本篇博客篇幅较长,但这只是 GCD 的入门介绍。事实上,我们讨论的内容还算基础,仅涉及了队列和任务的创建,以及它们如何组合在一起工作。然而,掌握这些基础概念是深入理解 GCD 并有效应用其强大功能的第一步。
未来还有更多复杂的机制和高级用法等待我们探索。希望通过这篇文章,你对 GCD 有了初步的认识,以后的博客我们会继续探讨GCD以及其它并发编程相关的内容。
这篇关于并发编程 - GCD的任务和队列的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!