第7章:メモリモデルと順序付け
この章で学ぶこと
- C11のメモリモデル
- アトミック操作
- メモリオーダー
- happens-before関係
- データ競合と未定義動作
---
7.1 なぜメモリモデルが必要か
並行プログラミングの問題
マルチスレッドプログラムでは、複数のスレッドが同時にメモリにアクセスします。
int x = 0;
int y = 0;
/* スレッド1 */
x = 1;
y = 1;
/* スレッド2 */
int r1 = y;
int r2 = x;
直感的には r1 == 1 && r2 == 0 は不可能に見えますが、実際には起こりえます。
原因
---
7.2 アトミック型と操作
_Atomic 修飾子
#include <stdatomic.h>
_Atomic int counter = 0;
atomic_int counter2 = ATOMIC_VAR_INIT(0);
アトミック操作
atomic_store(&counter, 42);
int value = atomic_load(&counter);
atomic_fetch_add(&counter, 1);
複合代入
counter++; /* アトミック */
counter += 5; /* アトミック */
---
7.3 メモリオーダー
6種類のメモリオーダー
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;
各オーダーの意味
relaxed: 順序保証なし。アトミック性のみ。
acquire: このロード以降の読み書きが、このロード前にリオーダーされない。
release: このストア以前の読み書きが、このストア後にリオーダーされない。
acq_rel: acquire と release の両方。
seq_cst: 逐次一貫性。最も厳しい。デフォルト。
使用例
/* プロデューサー・コンシューマー */
atomic_store_explicit(&data_ready, 1, memory_order_release);
while (atomic_load_explicit(&data_ready, memory_order_acquire) == 0)
;
/* data にアクセス */
---
7.4 happens-before関係
定義
操作Aが操作Bより先に「happens-before」する場合、Aの効果はBから見える。
成立条件
例
/* スレッド1 */
data = 42; /* A */
atomic_store_explicit(&flag, 1, memory_order_release); /* B */
/* スレッド2 */
while (atomic_load_explicit(&flag, memory_order_acquire) == 0) /* C */
;
int local = data; /* D */
B happens-before C、したがって A happens-before D。local は 42。
---
7.5 データ競合
定義
規格 5.1.2.4p25:
> Two expression evaluations conflict if one of them modifies a memory location and the other one reads or modifies the same memory location.
データ競合は、競合するアクセスがあり、happens-before関係がない場合に発生します。
データ競合は未定義動作
int x = 0;
/* スレッド1 */
x = 1;
/* スレッド2 */
int y = x;
/* データ競合 → 未定義動作 */
解決方法
_Atomic int x = 0;
/* スレッド1 */
atomic_store(&x, 1);
/* スレッド2 */
int y = atomic_load(&x);
/* OK: アトミック操作 */
---
7.6 ミューテックスと条件変数
C11スレッド(オプション)
#include <threads.h>
mtx_t mutex;
mtx_init(&mutex, mtx_plain);
mtx_lock(&mutex);
/* クリティカルセクション */
mtx_unlock(&mutex);
POSIXスレッド
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
/* クリティカルセクション */
pthread_mutex_unlock(&mutex);
---
7.7 この章のまとめ
C11メモリモデル
データ競合
---
確認問題
問題1
データ競合が未定義動作である理由は?
解答
規格がデータ競合を未定義動作と定義しているため。最適化や実行順序の変更に自由度を与えるため。
問題2
memory_order_seq_cst と memory_order_relaxed の違いは?
解答
seq_cst: 逐次一貫性。すべてのスレッドが同じ順序で操作を観測。最も厳しい。relaxed: アトミック性のみ保証。順序の保証なし。最も緩い。
---
次の章では、標準ライブラリの仕様詳細について学びます。