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

Swift基于DispatchSemaphore的线程安全数组跨线程传数据死锁咨询

问题根源分析

1. 信号量不可重入导致的内部死锁

你最初的实现中,unshift、pop方法执行时已经通过wait()获取了信号量,方法内部调用的count()又会再次尝试获取同一个信号量。DispatchSemaphore属于不可重入锁,值为1的信号量被持有后,同一个线程再次调用wait()会直接进入永久等待状态,这是初始版本死锁的核心原因。

你后续修改count方法的加锁逻辑虽然解决了重入问题,但仍然存在第二个设计缺陷:

2. 消费线程空转抢占锁,导致生产线程无法获取资源

你的消费逻辑采用while(true)死循环实现,当数组为空时,unshift方法仍然会高频执行,持续抢占、释放信号量,高并发场景下生产线程的append操作会始终抢不到信号量,表现为双向卡住的类死锁状态。

修复方案

基础修复版本

首先调整SyncArray内部逻辑,持有锁的场景下直接访问内部变量,不要调用其他加锁方法避免重入,同时把内部存储数组改为私有禁止外部直接访问:

public class SyncArray<T> {
    // 改为私有,避免外部直接操作导致线程不安全
    private var dataArray = [T]()
    private var semaphore = DispatchSemaphore(value: 1)
    
    public init() {}
    
    private func wait() { semaphore.wait() }
    private func signal() { semaphore.signal() }
    
    public func count() -> Int {
        wait(); defer { signal() }
        return dataArray.count
    }
    
    public func unshift() -> T? {
        wait(); defer { signal() }
        // 持有锁的状态下直接访问内部数组,无需调用加锁的count方法
        guard !dataArray.isEmpty else { return nil }
        return dataArray.removeFirst()
    }
    
    public func pop() -> T? {
        wait(); defer { signal() }
        guard !dataArray.isEmpty else { return nil }
        return dataArray.popLast()
    }
    
    public func append(value: T) {
        wait(); defer { signal() }
        dataArray.append(value)
    }
}

其次优化消费线程逻辑,数组为空时增加短暂休眠,避免无意义的锁抢占:

DispatchQueue.global().async {
    while true {
        if let data = buff.unshift() {
            // 此处添加数据处理逻辑
            processData(data)
        } else {
            // 空数组时休眠1毫秒,降低锁抢占频率
            Thread.sleep(forTimeInterval: 0.001)
        }
    }
}

更高效的优化方案(可选)

如果需要避免空轮询的性能损耗,可以用DispatchCondition替代信号量实现生产者-消费者模型,数组为空时消费线程自动阻塞,有新数据时再被唤醒,不需要空转占用CPU资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 19:36:03