この記事の要点
- 低レベルなアトミック操作を Swift から直接使えるようにする新しいオープンソースパッケージ Swift Atomics(swift-atomics)が発表されました。並行データ構造などの同期機構を、他言語の実装を取り込まずに Swift だけで構築できるようにすることを目指しています。
- このパッケージは、Swift に C/C++ 風のメモリモデルを正式に採用した SE-0282 を土台にしています。アトミック操作は通常の変数を支配する排他的アクセスのルールに従わず、値がアトミック操作経由でのみアクセスされる限り、複数のスレッドから並行に実行できます。
- アトミックはきわめて誤りやすい低レベルな仕組みです。標準ライブラリの unsafe API と同様、使用は最小限にとどめ、できれば使わないことが推奨されます。
- アトミック操作はすべて lock-free な実装が保証されますが、wait-free は保証されません。
何が発表されたのか
Swift Atomics は、低レベルなアトミック操作を Swift コードから直接使えるようにするパッケージです。これにより、並行データ構造のような同期機構を、その実装を他言語からインポートすることなく Swift だけで組み立てられるようになります。
たとえば、アトミックなカウンタを複数スレッドから並行にインクリメントするコードは次のように書けます。
import Atomics
import Dispatch
let counter = ManagedAtomic<Int>(0)
DispatchQueue.concurrentPerform(iterations: 10) { _ in
for _ in 0 ..< 1_000_000 {
counter.wrappingIncrement(by: 1, ordering: .relaxed)
}
}
counter.load(ordering: .relaxed) // ⟹ 10_000_000
この例のアトミック操作は、通常の Swift 変数を支配する排他的アクセスのルールには従いません。値がアトミック操作経由でのみアクセスされる限り、複数の並行するスレッドからアトミック操作を実行できます。
これを可能にしているのが SE-0282 で、Swift に C/C++ 風のメモリモデルを正式に採用し、通常の Swift コードとアトミック操作がどう相互作用するかを(非公式に)規定しました。このパッケージの API の多くは、SE-0282 の以前の版で設計されたものに由来します。
何に使えるのか
このパッケージは、これまで Swift では手の届かなかったシステムプログラミングのユースケースを可能にします。特に、並行処理を管理するための、より高水準で扱いやすい機構を、他言語からの実装の取り込みに頼らず Swift だけで作れるようになります。
ただし、アトミックは他の低レベルな並行処理の機構にも増して正しく使うのが難しいことで知られています。標準ライブラリの unsafe API と同様、このパッケージの使用は最小限にとどめ、できれば使わないことが推奨されます。それでも必要な場合は、次のような指針が示されています。
- 新しいアルゴリズムを発明するのではなく、既に発表済みのアルゴリズムを実装する。
- アトミックを使うコードは、小さくレビューしやすい単位に隔離する。
- アトミックの構造体をインターフェースの型として引き回さない。
アトミックを扱うコードには細心の注意を払い、変更のたびに Thread Sanitizer を十分にかけることが勧められています。
サポートされるアトミック型
このパッケージは、AtomicValue プロトコルに適合する次の型に対してアトミック操作を実装します。
- 標準の符号付き整数型(
Int、Int64、Int32、Int16、Int8) - 標準の符号なし整数型(
UInt、UInt64、UInt32、UInt16、UInt8) Bool- 標準のポインタ型(
UnsafeRawPointer、UnsafeMutableRawPointer、UnsafePointer<T>、UnsafeMutablePointer<T>)とそれらの optional 版 Unmanaged<T>とその optional 版lowとhighの 2 つのUIntからなる特別なDoubleWord型(double-wide なアトミックプリミティブを提供)RawValueがアトミック型である任意のRawRepresentable型(単純なカスタムenum型など)AtomicReferenceプロトコルへの適合によってアトミックな利用を選択したクラスインスタンスへの強参照
特筆すべきは、アトミックな強参照を完全にサポートしている点です。これは、Swift の参照カウントによるメモリ管理モデルにそのまま馴染む、並行データ構造向けのメモリ回収手段を提供します(アトミックな強参照は DoubleWord 操作を使って実装されています)。
アトミックな強参照の一般的なユースケースの 1 つは、遅延初期化される(それ以外は定数の)クラス型の変数を作ることです。この単純な場合に汎用のアトミック参照を使うのは過剰なため、遅延初期化に特化したより効率的な ManagedAtomicLazyReference と UnsafeAtomicLazyReference も別途提供されます。これらは、並行コンテキストで安全に使えない lazy var の stored property の代替として役立ちます。
ラッパー型とアトミック操作
アトミックアクセスは、対応する通常の(非アトミックな)型とは区別された専用のアトミックストレージ表現を介して実装されます。たとえば上記カウンタの実際の整数値には直接アクセスできません。これにより、アトミック変数への誤った非アトミックアクセスが防がれ、内部で使われる標準 C アトミックライブラリとも整合します。
低レベルなポインタベースの操作を直接使うのではなく、アトミックストレージの準備・破棄を管理する高水準なラッパー型の利用が強く推奨されます。このバージョンでは次の 2 つが提供されます。
- 扱いやすくメモリ安全なジェネリッククラス
ManagedAtomic<T> - やや扱いにくいが柔軟な、手動でメモリ管理するジェネリック構造体
UnsafeAtomic<T>
ManagedAtomic はアトミック値ごとにクラスインスタンスの確保を必要とし、参照カウントでメモリを管理します。手軽な反面、確保や参照カウントのオーバーヘッドがすべてのユースケースに適するわけではありません。一方 UnsafeAtomic は、適切なストレージ型のポインタを取得できる任意のメモリ位置に対してアトミック操作を行えますが、その代わりにポインタがアクセス中ずっと有効であることを自分で保証する必要があります。
どちらのラッパーも、すべての AtomicValue 型に対して次のアトミック操作を提供します。
func load(ordering: AtomicLoadOrdering) -> Value
func store(_ desired: Value, ordering: AtomicStoreOrdering)
func exchange(_ desired: Value, ordering: AtomicUpdateOrdering) -> Value
func compareExchange(
expected: Value,
desired: Value,
ordering: AtomicUpdateOrdering
) -> (exchanged: Bool, original: Value)
整数型にはインクリメント・デクリメントやビット論理演算といった操作が追加され、Bool にも同様の論理演算が用意されています。
順序(ordering)の列挙は C/C++ 標準の std::memory_order に対応します。ただし consume 順序は公開されません(memory_order_consume はどの C/C++ コンパイラも実装しておらず、現行の C++ 標準ではその使用は推奨されていません)。このパッケージは load・store・update それぞれに適用できる順序のサブセットを表す 3 つの列挙を別々に提供します。
lock-free と wait-free
このパッケージが公開するアトミック操作はすべて lock-free な実装が保証されます。lock-free とは、操作が非ブロッキングであること、すなわち自身の処理を完了するために他スレッドの進行を待つ必要がないことを意味します。
ただし wait-free は保証されません。ターゲットプラットフォームの能力によっては、一部の操作が compare-and-exchange ループで実装される場合があります。複数スレッドが同じアトミック変数へのアクセスを繰り返し奪い合うと、不公平なスケジューリングが起こり、あるスレッドが何度も他スレッドに割り込まれて操作を任意の回数リトライさせられることがあります。とはいえ、専用の wait-free な CPU 命令が利用できる場合は、LLVM と Clang がサポートする範囲で、すべてのアトミック操作がそれらに直接マッピングされます。
導入・今後の位置づけ
発表時点で示されていた今後の構想であり、実現を約束するものではありません。
当面は、より多くのアトミック型・操作を追加してパッケージを充実させるとともに、テストスイートの改善によって正しさと性能に関する想定を検証していくとされていました。具体的には、並行データ構造でよくある問題を解くのに役立つ tagged atomics や、よく要望されるアトミックな浮動小数点演算のサポートが検討対象として挙げられています。
Swift Atomics は完全なオープンソースプロジェクトで、GitHub 上で開発されています。議論は Swift フォーラムの Atomics カテゴリで行えます。