Swiftでは、スレッドセーフを確保するために、主にGCD(Grand Central Dispatch)やディスパッチキュー(DispatchQueue)、NSLock、およびSwift 5.5以降のactorなどの最新のメカニズムが使用されます。
基本的な考え方 – 変更可能なデータへのアクセスは、厳密に直列キューを介して行うか、または同期化装置(ロック)を使用します。
GCDの例:
class ThreadSafeArray<Element> { private var array: [Element] = [] private let queue = DispatchQueue(label: "com.example.arrayQueue", attributes: .concurrent) func append(_ item: Element) { queue.async(flags: .barrier) { self.array.append(item) } } func get(index: Int) -> Element? { var result: Element? queue.sync { result = self.array.indices.contains(index) ? self.array[index] : nil } return result } }
現代的なアプローチ: Actors (Swift 5.5+):
actor SafeCounter { private var value = 0 func increment() { value += 1 } func get() -> Int { return value } }
どちらのアプローチもデータ競合を避け、状態の一貫性を維持することを可能にします。
質問:
メインキュー内で
DispatchQueue.syncを使用した場合、何が起こるか、なぜですか?
回答: デッドロックが発生します(Deadlock)、なぜならメインキューはすでにタスクを実行中であり、同じキュー上の同期操作の完了を待っているからです。その結果、キューは現在のタスクが完了するまで次のタスクを処理できなくなりますが、それは決して起こりません。
例:
DispatchQueue.main.sync { // デッドロック: この行は絶対に実行されません }
物語
プロジェクトでは、複数のスレッドから同期なしで同時にアクセスされる可変集合が使用されていました。これにより、アプリケーションがクラッシュし、データ競合による捕らえどころのないバグが発生しました: 時折、要素が失われ、時には配列の範囲を超えることがありました。
物語
ある開発者が、ランタイムでメインキューに対してDispatchQueue.syncを不正に使用したため、デッドロックが発生し、ユーザーのUIが完全に停止しました。緊急の修正とリリースのロールバックが必要でした。
物語
NSLockを使用してクラスをスレッドセーフにしようとした際、メソッドのすべての出口パス(エラーの場合やguard内のreturnなど)に対してロック/アンロックを実装するのを忘れたため、デッドロックの可能性が生じ、アプリケーションのパフォーマンスに重大な問題が発生しました。