複数のスレッドが数を増やす場合、カウンターのような単純なもの。スレッドが待機する必要があるため、相互排他ロックは効率を低下させる可能性があることを読みました。だから、私にとって、アトミックカウンターは最も効率的ですが、内部的には基本的にロックであると読みましたか?だから、どちらが他の方法よりも効率的であるのか混乱していると思います。
アトミック操作がサポートされているカウンターがある場合、ミューテックスよりも効率的です。
技術的には、アトミックはほとんどのプラットフォームでメモリバスをロックします。ただし、次の2つの改善点があります。
アトミック操作はプロセッサーのサポート(比較およびスワップ命令)を活用し、ロックをまったく使用しませんが、ロックはよりOSに依存し、たとえばWinとLinuxで異なる動作をします。
ロックは実際にスレッドの実行を一時停止し、他のタスクのためにCPUリソースを解放しますが、スレッドの停止/再起動時に明らかなコンテキスト切り替えのオーバーヘッドが発生します。それどころか、アトミック操作を試行するスレッドは、成功するまで待機せずに試行を続ける(いわゆるビジー待機)ため、コンテキスト切り替えのオーバーヘッドは発生しませんが、CPUリソースは解放されません。
要約すると、一般に、スレッド間の競合が十分に少ない場合、アトミック操作は高速です。コンテキスト切り替えとビジー待機の間で最も低いオーバーヘッドが何であるかを知る他の信頼できる方法はないので、間違いなくベンチマークを行う必要があります。
最小限の(標準に準拠した)ミューテックスの実装には、2つの基本的な要素が必要です。
C++標準が必要とする 'synchronizes-with'関係のため、これよりも簡単にする方法はありません。
最小限の(正しい)実装は次のようになります。
class mutex {
std::atomic<bool> flag{false};
public:
void lock()
{
while (flag.exchange(true, std::memory_order_relaxed));
std::atomic_thread_fence(std::memory_order_acquire);
}
void unlock()
{
std::atomic_thread_fence(std::memory_order_release);
flag.store(false, std::memory_order_relaxed);
}
};
その単純さ(実行のスレッドを中断することはできません)により、競合が少ない場合、この実装はstd::mutex
を上回る可能性があります。しかし、それでも、このミューテックスによって保護されている各整数の増分には、次の操作が必要であることが簡単にわかります。
atomic
ストアatomic
compare-and-swap(読み取り-変更-書き込み)でミューテックスを取得します(複数回可能)単一の(無条件の)読み取り-変更-書き込み(たとえば、std::atomic<int>
)でインクリメントされるスタンドアロンfetch_add
と比較する場合、アトミック操作(同じ順序を使用) model)は、ミューテックスが使用されるケースよりも優れています。
Javaのアトミック変数クラスは、プロセッサーが提供する比較およびスワップ命令を利用できます。
違いの詳細な説明は次のとおりです。 http://www.ibm.com/developerworks/library/j-jtp11234/
アトミック整数はユーザーモードオブジェクトであり、カーネルモードで実行されるミューテックスよりもはるかに効率的です。アトミック整数のスコープは単一のアプリケーションであり、ミューテックスのスコープはマシンで実行中のすべてのソフトウェアを対象としています。
Mutex
は、Process level
でも相互排除を提供するカーネルレベルのセマンティックです。プロセス内(スレッドの場合)だけでなく、プロセスの境界を越えて相互排除を拡張するのに役立つことに注意してください。費用がかかります。
アトミックカウンター、たとえばAtomicInteger
はCASに基づいており、通常は成功するまで操作を試みます。基本的に、この場合、スレッドは値をアトミックにインクリメントまたはデクリメントするために競合または競合します。ここで、現在の値を操作しようとしているスレッドが使用している良好なCPUサイクルを確認できます。
カウンターを保持するため、AtomicInteger\AtomicLongがユースケースに最適です。
ほとんどのプロセッサは、アトミックな読み取りまたは書き込みをサポートしており、多くの場合、アトミックなcmp&swapをサポートしています。これは、プロセッサ自体が単一の操作で最新の値を書き込みまたは読み取りを行うことを意味し、特にコンパイラーが通常とほぼ同じ程度にアトミック操作を最適化できないため、通常の整数アクセスと比較して数サイクルの損失が発生する可能性があります。
一方、ミューテックスは出入りするコードの行であり、その実行中に同じ場所にアクセスする他のプロセッサは完全に停止するため、明らかに大きなオーバーヘッドが発生します。最適化されていない高レベルコードでは、ミューテックスの開始/終了とアトミックは関数呼び出しになりますが、ミューテックスの場合、ミューテックスの入力関数が戻る間、および終了関数が開始される間、競合するプロセッサはロックアウトされます。アトミックの場合、ロックアウトされるのは実際の操作の期間のみです。最適化はそのコストを削減するはずですが、すべてではありません。
インクリメントしようとしている場合、現在のプロセッサはおそらくアトミックなインクリメント/デクリメントをサポートしていますが、これは素晴らしいことです。
そうでない場合は、プロセッサーのアトミックcmp&swapを使用するか、ミューテックスを使用して実装されます。
ミューテックス:
get the lock
read
increment
write
release the lock
アトミックcmp&swap:
atomic read the value
calc the increment
do{
atomic cmpswap value, increment
recalc the increment
}while the cmp&swap did not see the expected value
このため、この2番目のバージョンにはループがあります(別のプロセッサがアトミックオペレーション間で値をインクリメントし、値が一致しなくなり、インクリメントが間違っている場合)が長くなる可能性があります(競合他社が多い場合)ミューテックスバージョンですが、ミューテックスバージョンにより、そのプロセッサはタスクを切り替えることができます。