スレッド間の同期を取る方法は複数あります。
OS のカーネルオブジェクト、ライブラリ関数、OS 依存の API など。
他にも CAS のような最低限の atomic 操作を使って Spinlock を作ることができます。
もちろんハードウエアスレッド環境に限ります。
C++11 対応に伴いコンパイラも新しい atomic 命令に移行しています。
例えば gcc の builtin 命令は __sync_ から __atomic_ となり、
clang にも同様に __c11_atomic_ があります。
Windows の場合は OS に Interlocked~ API がありますが、
VC++ が生成する同名の Intrinsic バージョンもあります。
こちらも C++11 に従いメモリバリアを指定できる命令が追加されているようです。
// CAS gcc __atomic_compare_exchange_*() clang __c11_atomic_compare_exchange_*() Windows _InterlockedCompareExchange_*()
・gcc 4.7.2 : Built-in functions for memory model aware atomic operations
・Clang 3.3 : Clang Language Extensions
・msdn : x86 Intrinsics List
↓各種 API による比較です。
(1) Windows Win8 x86 Win8 x64 ------------------------------------------------------- SpinLock(intrinsic) 5852 6353 CriticalSection 20894 22610 Win32 Mutex 2537901 3790403 (2) Linux Linux x86 Linux x64 Linux ARM ------------------------------------------------------- SpinLock(gcc atomic) 12712 10641 27977 pthread mutex 13693 11796 23807 (3) MacOS X MacOS X x86 MacOS X x64 ------------------------------------------------------- SpinLock(OSAtomic) 9505 9070 pthread mutex 563013 467785 // 実行時間 単位=us
3つのスレッドで同じカウンタをそれぞれ 50000回カウントしています。
100% 競合する状態で、ロック手段による違いを測定しています。
走らせた PC のスペックが異なることと、数値の変動が大きいため
単純な比較ができないのでご注意ください。
同じ OS 内で API による違いだけ見てください。
(1) Windows (Windows 8 + VS2012 Express)
カーネルオブジェクトである Mutex は非常に遅くなっています。
CriticalSection の方が2桁高速ですが、_Interlocked 命令を使った
SpinLock の方が更に速い結果となっています。
(2) Linux (Ubuntu 12.10 / 13.04)
x86/x64 は VMware Player によるテストなのでご了承ください。
特筆すべき点は pthread_mutex が SpinLock と全く変わらないか、
むしろ速いということです。
SpinLock を作る場合に atomic API の使い方を間違えると、
pthread_mutex よりも遅くなったり、ロックに失敗したりするようです。
X86/x64 の場合 gcc (4.7.2) builtin の __atomic_ でメモリアクセス方法に
__ATOMIC_SEQ_CST を指定すると pthread_mutex よりも低速でした。
__ATOMIC_RELAXED で pthread と同等になります。
また Legacy API ですが __sync_ を使った場合も pthread と同等の
速度となりました。
ARM (Cortex-A9) では RELAXED の場合メモリアクセスが競合し
正しくブロックされません。
上記テストは ARM の場合のみ SEQ_CST を指定しています。
また __sync_ も RELAXED 相当なためかロックされませんでした。
(3) MacOS X (10.8)
OSAtomic と phtread_mutex で大きな隔たりがあります。
同じ pthread の mutex でも、Linux と違い Windows のように
カーネルオブジェクトとして実装されている可能性があります。
API が同一でも OS と CPU によって挙動が違うことがわかりました。
Linux では pthread_mutex が最もよい選択ですが、
MacOS X では逆に pthread_mutex を避けた方が良いようです。
テストプログラムは下記のとおり。
templateclass LockTest { LockT lock; static int Counter; enum { MAX_LOOP= 50000 }; public: void thread_func() { for( int i= 0 ; i< MAX_LOOP ; i++ ){ lock.Lock(); Counter++; lock.Unlock(); } } void Run() { ClockTimer time; Counter= 0; Thread t0, t1, t2; t0.Run( this, &LockTest ::thread_func ); t1.Run( this, &LockTest ::thread_func ); t2.Run( this, &LockTest ::thread_func ); t0.Join(); t1.Join(); t2.Join(); time.Output(); assert( Counter == MAX_LOOP * 3 ); } };
関連?エントリ
・C++11 Rvalue Reference 右辺値参照