数组实现资源管理时出现意外竞态条件的技术问询
数组索引加锁仍出现竞态条件的原因分析
我正在设计一个简单的咖啡机系统,该系统使用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
相关产品推荐
相关产品推荐

