Swift GCD

基础知识

iOS 多线程实现方式

  • Grand Central Dispatch (GCD): 自动管理线程生命周期
  • NSOperation: 自动管理线程生命周期,基于 GCD 实现
  • NSThread: 手动管理线程生命周期

并发和并行

  • 并发: 并发不是真正的多线程,而是通过CPU(单核)在多个线程间快速切换调度,实现接近同时执行的效果
  • 并行: 并行是真正的多线程,多个线程能同时执行

同步和异步

  • 同步:当前线程里按顺序执行多项任务,且任务结束的顺序和任务开始执行的顺序是一致的。执行同步任务不会开启新线程,所有的任务都在当前线程执行。把一个同步任务分配出去后,当前线程会一直等到这个任务执行完才能接着运行后面的代码(因为同步任务本身就是分配给当前线程干的)
  • 异步:异步任务也是按顺序分配给线程的,但是可能会放在多个线程里执行的,所以每个任务结束的顺序有可能是随机的。执行异步任务可能会开启新线程,要具体情况具体分析。把一个异步任务分配出去后,当前线程不用等这个任务执行完就可以执行后面的代码,因为这个异步任务有可能交给别的线程去做了

串行队列和并行队列

  • 串行队列:按顺序分配任务,但是只有等上一个任务结束运行了,下一个任务才能分配出去
  • 并行队列:按顺序分配任务,下一个任务不必等上一个任务运行结束就能分配出去

也就是说,串行队列能够保证在分配某个任务的时候,在它之前分配出去的任务都被执行完成了。但是并行队列无法保证

执行下面代码:

let customConcurrentQueue = DispatchQueue(label: "custom", attributes: .concurrent)
customConcurrentQueue.async {
    print("当前线程 ->  thread: \(Thread.current)")
    customConcurrentQueue.sync {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}
输出:
当前线程 ->  thread: <NSThread: 0x6000000b9a80>{number = 8, name = (null)}
当前线程 ->  thread: <NSThread: 0x6000000b9a80>{number = 8, name = (null)}

可以看到,并行队列会把同步任务安排到当前线程里执行

执行下面的代码:

let customSerialQueue = DispatchQueue(label: "serial")  // 自定义一个串行队列
let customQueue2 = DispatchQueue(label: "queue2", attributes: .concurrent)  // 自定义一个并行队列
customSerialQueue.async {
    print("当前线程A ->  thread: \(Thread.current)")
    customQueue2.async(execute: item1)
    customQueue2.async(execute: item2)
}

customSerialQueue.async {
    print("当前线程B ->  thread: \(Thread.current)")
    customQueue2.async(execute: item3)
    customQueue2.async(execute: item4)
}

输出:
当前线程A ->  thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
当前线程B ->  thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item2 -> 0 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item3 -> 0 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item2 -> 1 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item3 -> 1 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item2 -> 2 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item3 -> 2 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item1 -> 0 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item3 -> 3 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item2 -> 3 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item3 -> 4 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item2 -> 4 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item1 -> 1 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item4 -> 0 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item1 -> 2 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item4 -> 1 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item1 -> 3 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item1 -> 4 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item4 -> 2 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item4 -> 3 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item4 -> 4 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}

可以看到,并行队列开启了四个线程去分别执行四个任务,导致了四个任务的执行时间和结束时间是随机的。四个任务能够同时进行,下一个任务不必等上一个任务完成就能被分配出去

如果把 customQueue2 改成串行队列:

let customSerialQueue = DispatchQueue(label: "serial")  // 自定义一个串行队列
let customQueue2 = DispatchQueue(label: "queue2")
customSerialQueue.async {
    print("当前线程A ->  thread: \(Thread.current)")
    customQueue2.async(execute: item1)
    customQueue2.async(execute: item2)
}

customSerialQueue.async {
    print("当前线程B ->  thread: \(Thread.current)")
    customQueue2.async(execute: item3)
    customQueue2.async(execute: item4)
}

输出:
当前线程A ->  thread: <NSThread: 0x600001fc4700>{number = 8, name = (null)}
当前线程B ->  thread: <NSThread: 0x600001fc4700>{number = 8, name = (null)}
item1 -> 0 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item1 -> 1 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item1 -> 2 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item1 -> 3 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item1 -> 4 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 0 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 1 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 2 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 3 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 4 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 0 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 1 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 2 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 3 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 4 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 0 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 1 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 2 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 3 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 4 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}

可以看到,串行队列虽然能开启新的线程,但是它只开启了一个线程,保证了任务执行和结束的顺序是固定的

从队列中分配出去的任务最终还是要线程来执行的。如果没有线程来接受这个任务,那这个任务或许会一直不被分配出去。而并行队列和串行队列的区分,应该是很大程度上取决于他们开辟线程的能力以及能够开辟线程的个数

线程和调度队列

调度队列 (DispatchQueue)

首先需要明确,线程 ≠ 队列。如果把对各项任务的处理比做一个工厂的话,那么线程是处理任务的机器,而队列则是将记录任务并将任务分配给机器的人

队列遵循先进先出 (First in First out, FIFO) 规则,可以保证在执行某个任务时,排他前面的任务肯定都被执行了。队列有串行和并行之分,对于串行队列,GCD会指定一条线程来执行队列中的任务;对于并行队列,GCD会分配多条线程来执行其中的任务

队列有三种类型:主队列、全局队列、自定义队列

主队列 (Main Queue)

程序运行的当前队列就是主队列,是唯一的。主队列与主线程相关联,主队列上的任务运行在主线程上。与UI相关的操作也必须放在主队列上。通过下述代码获取主队列:

let mainQueue = DispatchQueue.main
全局队列 (Global Queue)

全局队列是系统放在后台供所有 App 共享使用的队列,是并行队列。获取方式是

let globalQueue = DispatchQueue.global()
自定义队列 (Custom Queue)

自定义队列默认是串行队列。若初始化时指定 attributes 参数为 concurrent,则可创建并行队列

let customSerialQueue = DispatchQueue(label: "serial")  // 自定义队列,默认为串行

let customQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)   // 自定义并行队列

let customQoSQueue = DispatchQueue(label: "", qos: .userInteractive, attributes: .concurrent)   // 自定义并行队列,并指定队列优先级

参数含义分别是:

  • label: 队列标识,一般设置为 com.domain.somename,用于区分和调试
  • attributes: 队列属性,默认为串行。concurrent为并发队列。initiallyInactive表示该队列不会自动执行,需要开发者调用activate()(开始执行)、suspend()(挂起)、resume()(继续执行)手动管理相应的状态
  • qos: 即 Quality of Service, 是出于能效考虑而设置的优先级,一般走默认即可。qos有以下几种可选项(优先级从高到低):
  • userInteractive: 与用户交互有关的任务,优先级最高
  • userInitiated: 用户发起的任务,优先级比较高
  • default: 默认优先级。全局队列就是这个优先级
  • utility: 工具类型的任务,不需要得到立即响应。
  • background: 后台任务、不太重要的任务,优先级比较低
  • unspecified: 不确定的任务,优先级最低。这个优先级的任务有可能得不到处理

QoS 的设置可以参考:Energy Efficiency Guide for iOS Apps

调度任务 (DispatchWorkItem)

把要执行的代码写成一个闭包,赋给一个变量,方便管理任务。初始化代码如下:

// 走默认配置
let workItem1 = DispatchWorkItem {
    // some code
    ...
}

// 自定义配置
let workItem2 = DispatchWorkItem(qos: .userInteractive, flags: .barrier) {
    // some code
    ...
}

其中初始化参数 qos 与 DispatchQueue 类似。

「当前线程」

在本文中,「当前线程」指执行代码的线程,比如执行以下代码都是在「当前线程」执行的

print("hello world")

for index in 1...4 {
    ...
}

DispatchQueue.main.async{
    // 这里面的代码就不一定执行在当前线程了
    ...
}

DispatchQueue.global().async(execute: item)

注意,DispatchQueue.main.async{} 这个外层代码是在「当前线程」执行的,但是里面的 block 就不一定是在「当前线程」执行的了,因为它有可能已经被分配给别的线程了

「当前线程」一般是主线程,除非自己开辟了新的线程,比如:

print(">>> 当前线程是主线程 ->  thread: \(Thread.current)")
let myThread = Thread {
    print(">>> 当前线程不是主线程 ->  thread: \(Thread.current)")
}
myThread.start()

任务队列的分配调度依赖「当前线程」。DispatchQueue 队列里面任务的分配调度肯定是「当前线程」执行的,但是分配出去的任务不一定是当前线程执行的。就好比指挥官负责把一个个任务分配给某个士兵,这个士兵负责完成分配给他的任务(当然,指挥官也会给自己分配任务)

执行以下代码

// 这几个 item 后面也会用到
let item1 = DispatchWorkItem {
    for i in 0...4 {
        print("item1 -> \(i) thread: \(Thread.current)")
    }
}

let item2 = DispatchWorkItem {
    for i in 0...4 {
        print("item2 -> \(i) thread: \(Thread.current)")
    }
}

let item3 = DispatchWorkItem {
    for i in 0...4 {
        print("item3 -> \(i) thread: \(Thread.current)")
    }
}

let item4 = DispatchWorkItem {
    for i in 0...4 {
        print("item4 -> \(i) thread: \(Thread.current)")
    }
}

print("当前线程 ->  thread: \(Thread.current)")

let customQueue = DispatchQueue(label: "custom", attributes: .concurrent)
customQueue.async(execute: item1)
customQueue.sync(execute: item2)
customQueue.async(execute: item3)

// 输出:
当前线程 ->  thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item2 -> 0 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item1 -> 0 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item2 -> 1 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item2 -> 2 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item1 -> 1 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item2 -> 3 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item1 -> 2 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item2 -> 4 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item1 -> 3 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item1 -> 4 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 0 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 1 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 2 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 3 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 4 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}

可以看到,「当前线程」是主线程,item1 是异步任务,分配给了一个非主线程来执行;item2 是一个同步任务,分给了当前线程——主线程来执行。由于当前线程优先处理同步任务,所以此时当前线程被阻塞来处理 item2。item1 和 item2 分别由两个线程同时处理。而由于当前线程在处理 item2 的时候,还没有执行到customQueue.async(execute: item3)这行代码,所以 item3 没有被分配出去,也就没有被执行。等当前线程处理完 item2 的时候,才会把 item3 分配出去

再执行下一段代码:

let ct = Thread {
    print("当前线程 ->  thread: \(Thread.current)")
    customQueue.async(execute: item1)
    customQueue.sync(execute: item4)
    customQueue.async(execute: item2)
}
ct.start()

// 输出
当前线程 ->  thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item4 -> 0 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 0 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item4 -> 1 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 1 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item4 -> 2 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 2 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item4 -> 3 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 3 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item4 -> 4 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 4 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item2 -> 0 thread: <NSThread: 0x144756780>{number = 6, name = (null)}
item2 -> 1 thread: <NSThread: 0x144756780>{number = 6, name = (null)}
item2 -> 2 thread: <NSThread: 0x144756780>{number = 6, name = (null)}
item2 -> 3 thread: <NSThread: 0x144756780>{number = 6, name = (null)}
item2 -> 4 thread: <NSThread: 0x144756780>{number = 6, name = (null)}

可以看到,「当前线程」非主线程,而是一个手动开辟的线程。item1、item4 和 item2 的处理方式与上面运行在主线程的代码类似。同步任务 item4 同样交给了当前线程来执行,item1 和 item2 由另外两个线程来处理。虽然当前线程并不是主线程,但它仍然在执行 item4 的时候被阻塞了,导致 item2 在 item4 执行完之后才被分配给另一个线程处理

基本使用

DispatchQueue 的同步/异步执行

取四种队列:

// 主队列
let mainQueue = DispatchQueue.main
// 全局队列
let globalQueue = DispatchQueue.global()
// 自定义并行队列
let customConcurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
// 自定义串行队列
let customSerialQueue = DispatchQueue(label: "serial")

对上述四个队列分别调用同步和异步方法执行 item1 ~ item4:

// 同步执行
someQueue.sync(execute: item1)
someQueue.sync(execute: item2)
someQueue.sync(execute: item3)
someQueue.sync(execute: item4)

// 异步执行
someQueue.async(execute: item1)
someQueue.async(execute: item2)
someQueue.async(execute: item3)
someQueue.async(execute: item4)

结果如下:

  同步执行 异步执行
主队列 如果当前线程是主线程,会死锁;
否则的话,按顺序打印
按顺序执行
全局队列 在当前线程按顺序打印 随机打印
自定义串行队列 在当前线程按顺序打印 按顺序打印
自定义并行队列 在当前线程按顺序打印 随机打印

DispatchGroup 的使用

调度组(DispatchGroup)可以用来管理多个队列的异步任务,常用方法有 notify(), enter()leave()

场景一:多个网络接口同时开始请求,请求都结束后通知主队列刷新 UI

执行下面代码:

let group = DispatchGroup()
let queue1 = DispatchQueue(label: "request1", attributes: .concurrent)
let queue2 = DispatchQueue(label: "request2", attributes: .concurrent)

queue1.async(group: group) {
    print("接口一开始请求")
    sleep(3)
    print("接口一请求成功")
}

queue2.async(group: group) {
    print("接口二开始请求")
    sleep(3)
    print("接口二请求成功")
}

//主队列刷新UI
group.notify(queue: DispatchQueue.main) {
    print("UI刷新成功 当前线程: \(Thread.current)")
}

输出:
接口一开始请求
接口二开始请求
接口二请求成功
接口一请求成功
UI刷新成功 当前线程: <_NSMainThread: 0x6000006cc4c0>{number = 1, name = main}

队列一和二模拟了两个网络接口的请求。当请求都结束后,通知到主线程刷新 UI

场景二:多个网络接口按顺序请求,请求结束后通知主队列刷新 UI

现有两个接口,接口二依赖于接口一,只有当接口一请求完成后,接口二才能开始请求

代码如下:

let group = DispatchGroup()
let queue1 = DispatchQueue(label: "request1", attributes: .concurrent)
let queue2 = DispatchQueue(label: "request2", attributes: .concurrent)

group.enter()
queue1.async {
    print("接口一开始请求")
    sleep(3)
    print("接口一请求成功")
    group.leave()
}

group.wait()
group.enter()
queue2.async {
    print("接口二开始请求")
    sleep(3)
    print("接口二请求成功")
    group.leave()
}

//主队列刷新UI
group.notify(queue: DispatchQueue.main) {
    print("UI刷新成功 当前线程: \(Thread.current)")
}

输出:
接口一开始请求
接口一请求成功
接口二开始请求
接口二请求成功
UI刷新成功 当前线程: <_NSMainThread: 0x6000031544c0>{number = 1, name = main}

注意代码中 wait() 方法的使用。注意

queue.async(group: group) {
    // some code
}

可以写成

group.enter()
queue.async {
    // some code
    group.leave()
}

enterleave 必须成对出现

死锁分析

首先来分析一下,串行队列造成死锁的两种途径:

  1. 串行队列异步任务嵌套同步任务

下面这段代码在串行队列的一个异步任务中嵌套了一个同步任务,造成了死锁

// 自定义串行队列
let customSerialQueue = DispatchQueue(label: "serial")
customSerialQueue.async {
    print("当前线程 ->  thread: \(Thread.current)")
    customSerialQueue.sync {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}

输出:
当前线程 ->  thread: <NSThread: 0x600003020840>{number = 7, name = (null)}

这段代码会运行第一个 print 函数,随后卡死。根据第一个 print 的输出可以看到,这个串行队列的异步任务放到了一个非主线程中执行。把第一个 async block 标记为任务A,里面的 sync block 标记为任务B。任务A被执行后,一个新的线程被创建出来了,A里面的代码会放在这个新线程里面执行。当执行到 sync 的时候,同步任务会分给当前线程执行,而且会阻塞当前线程。但是由于队列是串行队列,所以任务B被分出去的条件是任务A执行完了;而当前线程被阻塞了,要想执行完任务A,就得执行完任务B。这就导致了两个任务互相等待,造成死锁

  1. 串行队列同步任务嵌套同步任务

下面这段代码在串行队列的一个同步任务中嵌套了另一个同步任务,造成了死锁

// 自定义串行队列
let customSerialQueue = DispatchQueue(label: "serial")
customSerialQueue.sync {
    print("当前线程 ->  thread: \(Thread.current)")
    customSerialQueue.sync {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}
输出:
当前线程 ->  thread: <_NSMainThread: 0x600001a044c0>{number = 1, name = main}

这段代码也会运行第一个 print 函数,随后卡死,并且 playground 会报错。由于同步任务会安排给当前线程执行,所以上面代码的第一个 sync 的 block 会分给主线程执行。由于串行队列只有在一个任务执行完成后才会分配下一个任务,所以当主线程执行完第一个 print 后,会告诉这个自定义串行队列去分配下一个同步任务(这时候主线程执行的是「分配队列任务」的任务,有点绕圈…)。但是又由于串行队列只能等上一个任务完成了才能分配下一个任务,但是下一个任务又包含在上一个任务里面。下一个任务是个同步任务,只能给当前线程执行,而且还会阻塞当前线程。上一个任务永远不会完成,下一个任务永远不会分配出去。这样就造成了两个任务互相等待,从而死锁

由于主队列是一个特殊的串行队列,所以上述对串行队列死锁的分析同样适用于主队列

之前在某篇文章上看到这句话:「在主队列中不能混入同步任务,否则会引起死锁。」❌
这句话是不对的,或者说是不完整的。正确的应该是,当前线程是主线程时,在主队列上混入同步任务会造成死锁

运行下面这段代码:

let ct = Thread {
    print("当前线程 ->  thread: \(Thread.current)")
    mainQueue.sync(execute: item2)
}
ct.start()

输出:
当前线程 ->  thread: <NSThread: 0x6000015d1500>{number = 8, name = (null)}
item2 -> 0 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}
item2 -> 1 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}
item2 -> 2 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}
item2 -> 3 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}
item2 -> 4 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}

上面代码在主队列安排了一个同步任务,但是没有死锁。这是因为在 Thread{} 里面,当前线程不是主线程,所以主队列混入同步任务没有造成死锁

那么问题来了,为什么串行队列嵌套异步任务不会引起死锁? 为什么并行队列嵌套同步任务不会引起死锁?

分析如下:

  1. 串行队列嵌套异步任务

执行下面代码

let customSerialQueue = DispatchQueue(label: "serial")
customSerialQueue.sync {
    print("当前线程 ->  thread: \(Thread.current)")
    customSerialQueue.async {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}

输出:
当前线程 ->  thread: <_NSMainThread: 0x600003ab04c0>{number = 1, name = main}
当前线程 ->  thread: <NSThread: 0x600003a90600>{number = 8, name = (null)}

可以看到,里层嵌套的异步任务是在新开辟的线程上执行的,而且异步任务不需要等待里面的代码执行完就能返回,不会造成任务间的互相等待,所以不会有死锁产生

  1. 并行队列嵌套同步任务

执行下面的代码

let customQueue = DispatchQueue(label: "custom", attributes: .concurrent)
customQueue.async {
    print("当前线程 ->  thread: \(Thread.current)")
    customQueue.sync {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}

输出:
当前线程 ->  thread: <NSThread: 0x6000028d5940>{number = 4, name = (null)}
当前线程 ->  thread: <NSThread: 0x6000028d5940>{number = 4, name = (null)}

里层嵌套的同步任务同样是在一个新开辟的线程上进行的,新线程执行完第一个 print 后,接着执行嵌套的 sync block。由于队列是并行队列,所以里面的同步任务不需要等外面的任务结束就能分配出去,相当于内层代码按顺序执行了,不会产生死锁