memory-model について
memory-model は、C++ と Rust のメモリモデルを扱うエージェント向けスキルです。メモリ順序、happens-before、アトミック操作、フェンスを解説し、ロックフリー構造の実践パターンも示します。
主な機能
- 01C++ と Rust のメモリ順序解説
- 02happens-before 関係の説明
- 03アトミック操作とフェンスの案内
- 04ロックフリー構造の実践パターン
- 05よくある誤りと修正方法
活用シーン
- 01アトミック操作の順序選択
- 02acquire-release と seq_cst の理解
- 03スレッド間でのデータ公開
- 04ロックフリーキューやスピンロックの実装
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill memory-modelインストール後、Claude Code で/memory-modelを入力して読み込まれたか確認します。
同梱ファイルreferences/cpp-memory-ordering.md
詳細
メモリモデル
目的
C++ と Rust のメモリモデルについて、メモリ順序、happens-before 関係、アトミック操作、フェンス、ロックフリーデータ構造の実践的なパターンをエージェントに案内します。
トリガー
- 「C++ のメモリモデルとは何ですか?」
- 「アトミック操作にはどのメモリ順序を使えばよいですか?」
- 「acquire-release と seq_cst の違いは何ですか?」
- 「C++ で std::atomic を使うにはどうすればよいですか?」
- 「Rust のアトミック操作における acquire/release とは何ですか?」
- 「ロックフリーキューを実装するにはどうすればよいですか?」
ワークフロー
1. メモリ順序の概要
最新の CPU やコンパイラーは、性能向上のために操作の順序を入れ替えます。メモリモデルでは、許可される順序の入れ替えと、同期の実現方法を規定します。
Ordering strength (weakest to strongest):
Relaxed < Release/Acquire < AcqRel < SeqCst
Stronger ordering = more synchronization = more correct, but slower
Weaker ordering = fewer barriers = faster, but needs careful analysis
2. メモリ順序
| 順序 | C++ | Rust | 意味 |
|---|---|---|---|
| Relaxed | memory_order_relaxed | Ordering::Relaxed | 順序の保証はなく、アトミック性のみ保証されます |
| Consume | memory_order_consume | (use Acquire) | データ依存関係による順序付け |
| Acquire | memory_order_acquire | Ordering::Acquire | 対応する release より前のすべての書き込みが、このロードから見えます |
| Release | memory_order_release | Ordering::Release | このストアより前のすべての書き込みが acquire から見えるようになります |
| AcqRel | memory_order_acq_rel | Ordering::AcqRel | RMW 操作で acquire と release の両方を行います |
| SeqCst | memory_order_seq_cst | Ordering::SeqCst | すべての seq_cst 操作に対して全順序を保証します |
3. C++ std::atomic
#include <atomic>
#include <thread>
std::atomic<int> counter{0};
std::atomic<bool> ready{false};
// Producer thread
void producer() {
data = 42; // (1) write data
ready.store(true, std::memory_order_release); // (2) signal
}
// Consumer thread
void consumer() {
while (!ready.load(std::memory_order_acquire)); // (3) wait
assert(data == 42); // (4) guaranteed to see (1)
}
acquire-release の保証:スレッド A が X に release ストアを行い、スレッド B が A の値を読み取る acquire ロードを行った場合、release より前に A が行ったすべての書き込みは、acquire より後に B から見えるようになります。
4. 適切な順序の選択
Use case?
├── Counter (just needs atomicity, order irrelevant) → Relaxed
├── Reference counting (decrement + final check) → AcqRel (dec), Acquire (load 0 check)
├── Publish data from one thread to another → Release (store), Acquire (load)
├── Mutual exclusion / mutex implementation → AcqRel / SeqCst
├── Lock-free queue multiple producers/consumers → SeqCst (safest to start)
└── Sequence number check (simple flag) → Release + Acquire
5. よくあるパターン
// Pattern 1: Spinlock
class Spinlock {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire))
; // spin
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
// Pattern 2: Reference counting
class RefCounted {
std::atomic<int> refcount{1};
public:
void addref() {
refcount.fetch_add(1, std::memory_order_relaxed); // only need atomicity
}
void release() {
if (refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
// AcqRel ensures we see all writes from other releasers
delete this;
}
}
};
// Pattern 3: One-time initialisation
class LazyInit {
std::atomic<void*> ptr{nullptr};
std::mutex mtx;
public:
void* get() {
void* p = ptr.load(std::memory_order_acquire);
if (p == nullptr) {
std::lock_guard lock(mtx);
p = ptr.load(std::memory_order_relaxed);
if (p == nullptr) {
p = create();
ptr.store(p, std::memory_order_release);
}
}
return p;
}
};
6. Rust のアトミック操作
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
// Simple counter
let counter = Arc::new(AtomicUsize::new(0));
// Increment
counter.fetch_add(1, Ordering::Relaxed);
// Read
let val = counter.load(Ordering::Relaxed);
// Publish/subscribe pattern
static READY: AtomicBool = AtomicBool::new(false);
// Publisher thread
unsafe { DATA = 42; } // Write data
READY.store(true, Ordering::Release); // Signal
// Subscriber thread
while !READY.load(Ordering::Acquire) {}
let d = unsafe { DATA }; // Safe: guaranteed to see publisher's write
7. フェンス
フェンスは、特定の変数に対するアトミック操作を行わずに順序付けを提供します。
// C++ fence — equivalent to a global memory barrier
std::atomic_thread_fence(std::memory_order_acquire); // Acquire fence
std::atomic_thread_fence(std::memory_order_release); // Release fence
// Typical use: multiple atomic writes then one fence
relaxed_atomic_a.store(1, std::memory_order_relaxed);
relaxed_atomic_b.store(2, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release); // barrier for all above
sentinel.store(true, std::memory_order_relaxed);
8. よくある間違い
| 間違い | 修正方法 |
|---|---|
| 公開/購読に Relaxed を使う | ストアには Release、ロードには Acquire を使います |
| どこでも SeqCst を使う | まずプロファイルを実行し、正しく動作する最も弱い順序を使います |
| 非アトミックなロードはアトミックではないことを忘れる | 共有されるすべての変更可能なデータには、アトミックまたはミューテックスが必要です |
C++ のスレッドセーフティに volatile を使う | volatile はメモリ順序を扱う機能ではありません。atomic を使います |
| SeqCst なしで逐次一貫性があると思い込む | プラットフォームごとに既定の一貫性が異なります |
メモリ順序の規則と happens-before のリファレンスについては、references/cpp-memory-ordering.md を参照してください。
関連スキル
skills/runtimes/sanitizersを使用 — TSan は、非アトミックアクセスを伴うデータ競合を検出します- Miri を使って Rust のメモリ順序違反を検出するには、
skills/rust/rust-sanitizers-miriを使用します - 生成されたフェンス命令を理解するには、
skills/low-level-programming/assembly-x86を使用します - スレッドの検査を通じて並行プログラムをデバッグするには、
skills/debuggers/gdbを使用します
コメント
Markdown 対応