You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在不同DispatchQueue操作同一变量的预期行为及线程安全疑问

多DispatchQueue并发写入变量的行为分析与解决方案

一、无同步保护时的预期行为

当多个DispatchQueue(不管是并发队列、串行队列还是当前队列)对同一未加保护的变量执行写入操作时,行为完全未定义。别被i += 1的表象骗了,它本质是三步操作:读取当前值、加1、写回新值。在并发场景下,多个线程可能同时读到同一个旧值,最后写回的结果会重复,导致最终的i远小于预期的40,甚至出现值混乱、重复输出的情况。

二、为什么Playground里看起来是串行的?

Playground的运行环境和真实App不一样:

  • 它为了方便调试,会限制并发执行的力度,甚至可能在某些场景下把异步任务串行化执行。
  • 你的代码里每个任务只有10次简单操作,执行速度极快,就算并发启动,也可能因为执行太快看起来像是串行完成,但这只是巧合,绝对不能代表真实App的运行情况。

三、必须加同步保护吗?

是的,必须加同步机制保证线程安全。在真实App的并发场景下,不加保护一定会出现数据竞争、值错误的问题。

四、Swift里常用的线程安全方案

1. 用串行队列做同步

专门用一个串行队列处理对i的所有读写操作,确保同一时间只有一个操作访问变量:

import Foundation

let syncQueue = DispatchQueue(label: "sync.i.queue")
var i = 0

// 写入操作
syncQueue.async {
    i += 1
    print("async 1-1: ", i)
}

// 读取操作(如果需要)
syncQueue.sync {
    print("当前i的值: ", i)
}

或者在原有并发队列里用屏障操作,保证写入时其他操作等待:

let q1 = DispatchQueue(label: "test-1", attributes: .concurrent)
var i = 0

// 用屏障包裹写入逻辑,确保写入时队列里的其他任务暂停
q1.async(flags: .barrier) {
    for _ in 0..<10 {
        i += 1
        print("async 1-1: ", i)
    }
}

2. 使用NSLock手动加锁

通过显式的锁来控制变量的访问权限:

import Foundation

let lock = NSLock()
var i = 0

q1.async {
    for _ in 0..<10 {
        lock.lock()
        i += 1
        print("async 1-1: ", i)
        lock.unlock()
    }
}

3. 用Swift Actor(Swift 5.5+)

Actor是Swift原生的并发安全类型,自动保证内部状态的线程安全,不需要手动加锁:

import Foundation

actor Counter {
    private var i = 0
    
    func increment() -> Int {
        i += 1
        return i
    }
}

let counter = Counter()

Task {
    for _ in 0..<10 {
        let value = await counter.increment()
        print("async 1-1: ", value)
    }
}

五、对DispatchQueue的理解纠正

  • 并发队列(.concurrent):会同时调度多个任务执行,任务执行顺序不确定,在多核CPU上是真正的并行执行。
  • 串行队列:同一时间只能执行一个任务,任务严格按添加顺序执行。
  • 不同队列之间的任务调度是独立的,所以你的代码里q1的两个并发任务、q2的任务、当前队列的同步任务,本来就可能同时执行,Playground的结果只是特殊环境下的巧合,不能当成普遍情况。

内容的提问来源于stack exchange,提问作者hj24

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 12:25:09