はじめに
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
この部分は、K
を Hashable
に制限しています。
✅ 理由:
-
Dictionary
のキーはHashable
である必要があるため、この制約を追加しています -
K
がHashable
でない場合、コンパイル時にエラーとなり、不正な型が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
を指定することで、型の安全性を強化できる