🔒

SwiftのMutexのwithLockとwithLockIfAvailableのデッドロック時の挙動

2025/02/02に公開

概要

SwiftのMutexで遊んでみて、withLockでデッドロックが起きるのか、withLockIfAvailableだったらどんな感じになるのかを検証してみたのでその備忘録です。

Mutexに関してはこちらを
https://zenn.dev/nitnc_tanaka/articles/e9372d8b1e83c1

https://developer.apple.com/documentation/synchronization/mutex

Mutex.withLockでデッドロックしてみる

以下のコードでデッドロックが起きることがわかりました。

operationAaLockbLockの順でロックを取得しようとし、operationBbLockaLockの順でロックを取得しようとするため、デッドロックが発生します。

数分待っても特に、終了する気配はありませんでした。

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