Swift-多线程GCD

232 阅读5分钟

1、队列 DispatchQueue

特点:先进先出

分类:串行队列、迸发队列

1.1 主队列

DispatchQueue.main

是一种串行队列(Serial),与主线程关联的调度队列,与UI相关的操作必须放在 主队列中执行。

1.2 全局队列

DispatchQueue.global()

是一种并行队列(Concurrent),用于处理并发任务。运行在后台线程,是系统内共享的全局队列。

1.3 自定义队列

DispatchQueue 是 Swift 中用于管理并发任务的类,通过它可以将任务放到不同的队列中执行。

  • 创建串行队列

把任务添加到串行队列中执行,无论是同步sync,还是异步方式async,都将按顺序执行。

通常用来确保任务按能顺序执行。线程同步。

let serialQueue = DispatchQueue(label: "com.example.serialQueue")

serialQueue.async { 
    print("Task 11111")
}

serialQueue.async { 
    print("Task 22222")
}

// 这两个任务会 按顺序执行,Task 1 -> Task 2

label: 队列的标识符,确保唯一性。标签仅用于调试或日志目的。

  • 创建迸发队列

加个参数 attributes

let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", 
                               attributes: .concurrent)

concurrentQueue.async { 
    print("Task A")
}

concurrentQueue.async { 
    print("Task B")
}

把任务添加到迸发队列中执行,若是用异步方式async,任务会并发执行,执行顺序不固定。若用同步方式sync则仍然是按顺序执行。

DispatchGroup

是 GCD 的任务组,可以很方便的管理多项任务。用来 管理一组异步任务的执行进度,非常适合在多个异步任务完成后,再统一处理结果。是常用的一个同步机制。

优点:不会造成线程阻塞!

“当 A、B、C 三个任务都完成后,再执行 D。”

常用方法:

  • enter():表示有一个任务加入组

  • leave():表示有一个任务离开组(该任务已完成)

  • notify(): 当所有任务都 leave 之后,会自动触发

  • wait():一直等待,直到调度组里所有任务都执行完毕或等待超时,阻塞当前线程。

场景1:等待多任务完成后统一回调

let group = DispatchGroup()
let queue = DispatchQueue.global()

group.enter()
queue.async {
    print("下载图片1开始")
    Thread.sleep(forTimeInterval: 2)
    print("下载图片1完成")
    group.leave()
}

group.enter()
queue.async {
    print("下载图片2开始")
    Thread.sleep(forTimeInterval: 3)
    print("下载图片2完成")
    group.leave()
}

// 所有任务完成后通知
group.notify(queue: .main) {
    print("所有下载完成,更新UI")
}

也可以简化写法:(不需要手动调用 enter()leave()

let group = DispatchGroup()
let queue = DispatchQueue.global()

queue.async(group: group) {
    Thread.sleep(forTimeInterval: 1)
    print("任务 A 完成")
}

queue.async(group: group) {
    Thread.sleep(forTimeInterval: 2)
    print("任务 B 完成")
}

group.notify(queue: .main) {
    print("A 和 B 都完成了")
}
  • 同步等待:group.wait()

如果你希望“阻塞”当前线程,直到所有任务执行完。(不建议在主线程中调用,会阻塞 UI)

let group = DispatchGroup()
let queue = DispatchQueue.global()

queue.async(group: group) {
    Thread.sleep(forTimeInterval: 1)
    print("任务1完成")
}

queue.async(group: group) {
    Thread.sleep(forTimeInterval: 2)
    print("任务2完成")
}

group.wait() // 阻塞当前线程,直到所有任务完成

print("所有任务完成后 执行")

可设置超时

let result = group.wait(timeout: .now() + 5)
if result == .timedOut {
    print("任务超时")
}

DispatchQueue.concurrentPerform

GCD提供的一种并发执行多个任务的方法。它用于高效地在多核处理器上并行执行一系列操作,特别是在处理重复任务时,利用多线程提高性能。

DispatchQueue.concurrentPerform(iterations: n) { index in
    // 并发执行的代码块
}

其中

• iterations: n:执行的次数。代码块 会循环执行n次。

• { index in }:这是一个闭包,闭包的参数 index 是当前执行的索引,从 0 到 n-1。在闭包内的代码将会并发执行多次,每次的索引都不相同。

  • 特点

1、并发执行:DispatchQueue.concurrentPerform 在内部会创建多个线程,并在这些线程中并发执行闭包任务。这是一个同步调用,意味着当前线程会阻塞,直到所有任务执行完毕。

因此,如果你希望任务在后台线程并发执行,可以在全局队列中使用它:

DispatchQueue.global().async {
    DispatchQueue.concurrentPerform(iterations: 10) { index in
        // 并发任务
    }
}

2、提高性能:DispatchQueue.concurrentPerform 可以利用多核 CPU,并发执行 计算密集型 或 IO 密集型任务,从而提高性能。适用于需要并行执行的大量重复任务,比如数组遍历、矩阵运算、大规模数据处理等计算密集型场景。

应用场景:在我们使用for循环的时候,其中的语句总是在前一个for完成之后才执行下一个。当执行大批量任务时,如果其中的任务相互独立,可以使用 DispatchQueue.concurrentPerform 来使用多线程平行的同步执行这些任务来节省时间。

死锁

线程安全

barrier

DispatchSemaphore

是 GCD 提供的一种 信号量(计数器),用来控制并发线程的数量,或者在多线程间实现同步。

它内部维护了一个整型计数值:

  • 大于 0 表示有可用资源,调用 wait() 会立即成功并将计数 减 1。
  • 等于 0 表示没有可用资源,此时调用 wait() 会阻塞当前线程,直到有资源释放。
  • 调用 signal() 会让计数加 1,并唤醒等待的线程。(如果有多个线程在等待,则唤醒其中一个)

使用场景

1、控制并发数量(限流)

let semaphore = DispatchSemaphore(value: 3)   // 最多允许3个并发任务

for i in 1...10 {
    DispatchQueue.global().async {
        semaphore.wait()   // 占用一个名额
        print("开始任务 \(i)")
        // do something....
        print("完成任务 \(i)")
        semaphore.signal() // 释放一个名额
    }
}

2、线程同步(等待异步任务完成)

func fetchDataSync(completion: @escaping (String) -> Void) {
    let semaphore = DispatchSemaphore(value: 0)
    var result: String?

    DispatchQueue.global().async {
        // 模拟网络请求
        Thread.sleep(forTimeInterval: 2)
        result = "数据返回"
        semaphore.signal() // 信号量+1,唤醒等待的线程
    }

    semaphore.wait() // 信号量=0,阻塞当前线程,等待异步完成, 才能继续执行
    completion(result ?? "")
}

// 调用
fetchDataSync { data in
    print("拿到数据: \(data)")
}

3、资源互斥访问

多个线程需要安全访问同一个变量。保证临界区只有一个线程在执行

let semaphore = DispatchSemaphore(value: 1) // 相当于互斥锁
var count = 0

DispatchQueue.concurrentPerform(iterations: 10) { _ in
    semaphore.wait()
    count += 1
    semaphore.signal()
}

print("最终 count = \(count)")   // 10

串行队列+计算属性

var b:Int{
    get{
        queue.sync {
            print("同步读取 thread = \(Thread.current)")
            return a
        }
    }
    set{
        queue.sync {
            print("同步写入 thread = \(Thread.current)")
            a = newValue
        }
    }
}

注意:

  • DispatchSemaphore 适合做简单的同步或限流,更复杂的情况可用 OperationQueueDispatchGroup
  • 避免死锁:如果 wait() 后忘记 signal(),线程可能永远卡住。
  • 不要在主线程阻塞:在主线程调用 wait() 可能导致界面卡死。

优先级