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

数组实现资源管理时出现意外竞态条件的技术问询

数组索引加锁仍出现竞态条件的原因分析

我正在设计一个简单的咖啡机系统,该系统使用milk、coffee、water三种资源。针对每个订单我会创建一个新线程,同时为每个订单线程再创建三个分别对应每种资源的线程,以避免串行访问资源时等待空闲资源。

我将资源存储在一个普通数组中,并为每个代表一种资源的数组索引添加锁进行保护,但开启TSAN后检测到了竞态条件。我确信不会有两个线程同时访问同一个数组索引,为了验证,我将资源改为单独变量存储后,竞态条件不再出现。请问为何给数组每个索引加锁仍会导致竞态条件?

使用数组存储资源(存在竞态条件)的代码

import Foundation

enum Resources: Int {
    case milk
    case coffe
    case water
}

struct Order {
    var id: Int
    var requiredMilk: Int
    var requiredCoffe: Int
    var requiredWater: Int
    
    init(requiredMilk: Int, requiredCoffe: Int, requiredWater: Int) {
        id = Int.random(in: 0...Int.max)
        self.requiredMilk = requiredMilk
        self.requiredCoffe = requiredCoffe
        self.requiredWater = requiredWater
    }
}

var resources: [Int] = []
let dispatchGroup = DispatchGroup()
let queue = DispatchQueue(label: "Ordering Queue", attributes: .concurrent)
let locks = [NSLock(), NSLock(), NSLock()]

func initiateResources(initialMilk: Int, initialCoffe: Int, initialWater: Int) {
    resources.append(contentsOf: [initialMilk, initialCoffe, initialWater])
}

func getAvailableMilk() -> Int {
    return resources[Resources.milk.rawValue]
}

func getAvailableCoffe() -> Int {
    return resources[Resources.coffe.rawValue]
}

func getAvailableWater() -> Int {
    return resources[Resources.water.rawValue]
}

func handleOrder(_ order: Order) {
    dispatchGroup.enter()
    // pushing the current order to the queue in order to be handled concurrenlty
    queue.async {
        let done = prepareOrder(order)
        if done {
            //print("Order with id \(order.id) prepared successfully")
        } else {
            //print("No sufficient resources for order with id \(order.id)")
        }
        dispatchGroup.leave()
    }
    
}

func prepareOrder(_ order: Order) -> Bool {
    var can = true
    let internalQueue = DispatchQueue(label: "internal queue", attributes: .concurrent)
    let internalDispatchGroup = DispatchGroup()
    let internalLock = NSLock()
    
    // opening a new thraed for each type of resources in order to not wait for a free resource
    if order.requiredMilk != 0 {
        internalDispatchGroup.enter()
        internalQueue.async {
            locks[Resources.milk.rawValue].lock()
            if getAvailableMilk() < order.requiredMilk {
                locks[Resources.milk.rawValue].unlock()
                internalLock.lock()
                can = false
                internalLock.unlock()
            } else {
                //Thread.sleep(forTimeInterval: 3)
                resources[Resources.milk.rawValue] -= order.requiredMilk
                locks[Resources.milk.rawValue].unlock()
            }
            internalDispatchGroup.leave()
        }
    }
    
    if order.requiredCoffe != 0 {
        internalDispatchGroup.enter()
        internalQueue.async {
            locks[Resources.coffe.rawValue].lock()
            if getAvailableCoffe() < order.requiredCoffe {
                locks[Resources.coffe.rawValue].unlock()
                internalLock.lock()
                can = false
                internalLock.unlock()
            } else {
                //Thread.sleep(forTimeInterval: 2)
                resources[Resources.coffe.rawValue] -= order.requiredCoffe
                locks[Resources.coffe.rawValue].unlock()
            }
            internalDispatchGroup.leave()
        }
    }
    
    if order.requiredWater != 0 {
        internalDispatchGroup.enter()
        internalQueue.async {
            locks[Resources.water.rawValue].lock()
            if getAvailableWater() < order.requiredWater {
                locks[Resources.water.rawValue].unlock()
                internalLock.lock()
                can = false
                internalLock.unlock()
            } else {
                //Thread.sleep(forTimeInterval: 1)
                resources[Resources.water.rawValue] -= order.requiredWater
                locks[Resources.water.rawValue].unlock()
            }
            internalDispatchGroup.leave()
        }
    }
    // making sure that the three resources threads funished successfully
    internalDispatchGroup.wait()
    return can
}

func endWork() {
    dispatchGroup.wait()
}

// note that I commented the thread.sleep in order to run faster, but every resource should have different serving time

initiateResources(initialMilk: 10000, initialCoffe: 10000, initialWater: 10000)
for _ in 0..<10 {
    handleOrder(Order(requiredMilk: 5, requiredCoffe: 5, requiredWater: 5))
}
endWork()
print(resources[0])
print(resources[1])
print(resources[2])

/*
 running 90 threads ech need 5 units from each resource ==> total of 450 units
 So reaming resources should be 9550 from each type if no confclicts occured
 */

使用单独变量存储资源(无竞态条件)的代码

import Foundation

enum Resources: Int {
    case milk
    case coffe
    case water
}

struct Order {
    var id: Int
    var requiredMilk: Int
    var requiredCoffe: Int
    var requiredWater: Int
    
    init(requiredMilk: Int, requiredCoffe: Int, requiredWater: Int) {
        id = Int.random(in: 0...Int.max)
        self.requiredMilk = requiredMilk
        self.requiredCoffe = requiredCoffe
        self.requiredWater = requiredWater
    }
}

var milkRsource = 0, coffeResource = 0, waterResource = 0
let dispatchGroup = DispatchGroup()
let queue = DispatchQueue(label: "Ordering Queue", attributes: .concurrent)
let milkLock = NSLock()
let coffeLock = NSLock()
let waterLock = NSLock()

func initiateResources(initialMilk: Int, initialCoffe: Int, initialWater: Int) {
    milkRsource =  initialMilk
    coffeResource = initialCoffe
    waterResource = initialWater
}

func getAvailableMilk() -> Int {
    return milkRsource
}

func getAvailableCoffe() -> Int {
    return coffeResource
}

func getAvailableWater() -> Int {
    return waterResource
}

func handleOrder(_ order: Order) {
    dispatchGroup.enter()
    // pushing the current order to the queue in order to be handled concurrenlty
    queue.async {
        let done = prepareOrder(order)
        if done {
            //print("Order with id \(order.id) prepared successfully")
        } else {
            //print("No sufficient resources for order with id \(order.id)")
        }
        dispatchGroup.leave()
    }
    
}

func prepareOrder(_ order: Order) -> Bool {
    var can = true
    let internalQueue = DispatchQueue(label: "internal queue", attributes: .concurrent)
    let internalDispatchGroup = DispatchGroup()
    let internalLock = NSLock()
    
    // opening a new thraed for each type of resources in order to not wait for a free resource
    if order.requiredMilk != 0 {
        internalDispatchGroup.enter()
        internalQueue.async {
            milkLock.lock()
            if getAvailableMilk() < order.requiredMilk {
                milkLock.unlock()
                internalLock.lock()
                can = false
                internalLock.unlock()
            } else {
                //Thread.sleep(forTimeInterval: 3)
                milkRsource -= order.requiredMilk
                milkLock.unlock()
            }
            internalDispatchGroup.leave()
        }
    }
    
    if order.requiredCoffe != 0 {
        internalDispatchGroup.enter()
        internalQueue.async {
            coffeLock.lock()
            if getAvailableCoffe() < order.requiredCoffe {
                coffeLock.unlock()
                internalLock.lock()
                can = false
                internalLock.unlock()
            } else {
                //Thread.sleep(forTimeInterval: 2)
                coffeResource -= order.requiredCoffe
                coffeLock.unlock()
            }
            internalDispatchGroup.leave()
        }
    }
    
    if order.requiredWater != 0 {
        internalDispatchGroup.enter()
        internalQueue.async {
            waterLock.lock()
            if getAvailableWater() < order.requiredWater {
                waterLock.unlock()
                internalLock.lock()
                can = false
                internalLock.unlock()
            } else {
                //Thread.sleep(forTimeInterval: 1)
                waterResource -= order.requiredWater
                waterLock.unlock()
            }
            internalDispatchGroup.leave()
        }
    }
    // making sure that the three resources threads funished successfully
    internalDispatchGroup.wait()
    return can
}

func endWork() {
    dispatchGroup.wait()
}

// note that I commented the thread.sleep in order to run faster, but every resource should have different serving time

initiateResources(initialMilk: 10000, initialCoffe: 10000, initialWater: 10000)
for _ in 0..<10 {
    handleOrder(Order(requiredMilk: 5, requiredCoffe: 5, requiredWater: 5))
}
endWork()
print(milkRsource)
print(coffeResource)
print(waterResource)

/*
 running 90 threads ech need 5 units from each resource ==> total of 450 units
 So reaming resources should be 9550 from each type if no confclicts occured
 */

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 18:51:02