0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Swift スレッドセーフなDictionary

Posted at

はじめに

Swift で Dictionary をマルチスレッド環境で安全に使用したい場合、スレッドセーフなデータ構造 が必要です。標準の Dictionary はスレッドセーフではないため、複数のスレッドから同時にアクセスすると、データ競合が発生する可能性があります。

なぜスレッドセーフな Dictionary が必要なのか?

スレッド競合の問題

通常の Dictionary は、複数のスレッドから同時に読み書きするとクラッシュする可能性 があります。

var dictionary: [String: Int] = [:]

DispatchQueue.concurrentPerform(iterations: 10) { index in
    dictionary["key\(index)"] = index // クラッシュの可能性あり
}

このコードでは、複数のスレッドが dictionary に対して同時に書き込みを行うため、データ競合が発生する可能性があります。

解決策:ロックを使う

スレッドセーフな Dictionary を作るには、データの読み書き時にロック(NSLock など)を適用し、排他制御 を行う必要があります。

スレッドセーフな SynchronizedDictionary を作ってみる

以下は、NSLock を使用したスレッドセーフな Dictionary の実装です。

スレッドセーフな Dictionary の実装

import Foundation

public final class SynchronizedDictionary<K, V>: @unchecked Sendable where K: Hashable {
    private var dictionary: [K: V] = [:]
    private let lock = NSLock()

    public init() {}

    public subscript(key: K) -> V? {
        get {
            lock.lock()
            defer { lock.unlock() }
            return dictionary[key]
        }
        set {
            lock.lock()
            defer { lock.unlock() }
            dictionary[key] = newValue
        }
    }
    
    public func removeValue(forKey key: K) {
        lock.lock()
        defer { lock.unlock() }
        dictionary.removeValue(forKey: key)
    }
    
    public func removeAll() {
        lock.lock()
        defer { lock.unlock() }
        dictionary.removeAll()
    }
}

@unchecked Sendable の意味

Swift では Sendable を使って型のスレッドセーフ性を保証します(マルチスレッドにしても値が壊れないことを保証する)。しかし、NSLock を使って手動でスレッドセーフを保証している場合、コンパイラはそれを自動的に検出できません。そのため、開発者が明示的に「この型はスレッドセーフである」と保証するために @unchecked Sendable を使います。

⚠️ 注意点:

  • @unchecked Sendableコンパイラのチェックをバイパスする ため、実際にスレッドセーフな実装になっているかは開発者の責任 となります。
  • NSLock などを適切に使用しないと、データ競合のバグが発生する可能性があります。

where の意味

where K: Hashable

この部分は、KHashable に制限しています。

理由:

  • Dictionary のキーは Hashable である必要があるため、この制約を追加しています
  • KHashable でない場合、コンパイル時にエラーとなり、不正な型が Dictionary に使われるのを防いでいます

SynchronizedDictionary の使い方

基本的な使用例

let syncDict = SynchronizedDictionary<String, Int>()

DispatchQueue.concurrentPerform(iterations: 10) { index in
    syncDict["key\(index)"] = index
}

print(syncDict["key5"] ?? "No value") // 正しく値が取得できる

スレッドセーフな Dictionary のおかげで、データ競合を防ぎながら安全に値を設定・取得できます。

非同期処理での活用

let syncDict = SynchronizedDictionary<String, String>()
let queue = DispatchQueue.global()

queue.async {
    syncDict["message"] = "Hello, World!"
}

queue.async {
    if let message = syncDict["message"] {
        print("Received: \(message)")
    }
}

異なるスレッドからの読み書きでも、データ競合を防ぐことができる。

4. まとめ

Swift の Dictionary はスレッドセーフではないため、マルチスレッド環境では注意が必要
NSLock を使うことで、スレッドセーフな Dictionary を実装可能
@unchecked Sendable は開発者がスレッドセーフを保証するためのマークであり、適切なロックが必要
where K: Hashable を指定することで、型の安全性を強化できる

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?