🔒
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