🔒
SwiftのMutexのwithLockとwithLockIfAvailableのデッドロック時の挙動
概要
SwiftのMutex
で遊んでみて、withLock
でデッドロックが起きるのか、withLockIfAvailable
だったらどんな感じになるのかを検証してみたのでその備忘録です。
Mutex
に関してはこちらを
Mutex.withLockでデッドロックしてみる
以下のコードでデッドロックが起きることがわかりました。
operationA
はaLock
→bLock
の順でロックを取得しようとし、operationB
はbLock
→aLock
の順でロックを取得しようとするため、デッドロックが発生します。
数分待っても特に、終了する気配はありませんでした。
withLockのコード
import Synchronization
let aLock = Mutex<Int>(2)
let bLock = Mutex<Int>(3)
func operationA() async -> Int {
aLock.withLock { a in
print("operationA: locking a")
for _ in 1...1_000_000 {}
print("operationA: tryLock b")
return bLock.withLock { b in
print("operationA: locking b")
return a + b
}
}
}
func operationB() async -> Int {
bLock.withLock { b in
print("operationB: locking b")
for _ in 1...1_000_000 {}
print("operationB: tryLock a")
return aLock.withLock { a in
print("operationB: locking a")
return a * b
}
}
}
async let operationingA = operationA()
async let operationingB = operationB()
let results = await [operationingA, operationingB]
print(results)
withLockIfAvailableで片方のみロック未取得でnilになる
一方で、withLockIfAvailable
を使用するとすぐに実行終了しました。片方即時にnilが返ってくるため、よほど中の処理が遅くない限り両方の処理がnil
で返ってくることはなさそうです。
withLockIfAvailableのコード
import Synchronization
let aLock = Mutex<Int>(2)
let bLock = Mutex<Int>(3)
func operationA() async -> Int? {
aLock.withLock { a in
print("operationA: locking a")
for _ in 1...1_000_000 {}
print("operationA: tryLock b")
return bLock.withLockIfAvailable { b in
print("operationA: locking b")
return a + b
}
}
}
func operationB() async -> Int? {
bLock.withLock { b in
print("operationB: locking b")
for _ in 1...1_000_000 {}
print("operationB: tryLock a")
return aLock.withLockIfAvailable { a in
print("operationB: locking a")
return a * b
}
}
}
async let operationingA = operationA()
async let operationingB = operationB()
let results = await [operationingA, operationingB]
print(results)
Discussion