比較とスワップが原子性を保証するという投稿をかなり読んだことがありますが、それがどのように行われるかはまだわかりません。以下に、比較とスワップの一般的な疑似コードを示します。
int CAS(int *ptr,int oldvalue,int newvalue)
{
int temp = *ptr;
if(*ptr == oldvalue)
*ptr = newvalue
return temp;
}
これは原子性をどのように保証しますか?たとえば、ミューテックスを実装するためにこれを使用している場合、
void lock(int *mutex)
{
while(!CAS(mutex, 0 , 1));
}
これはどのようにして2つのスレッドが同時にミューテックスを取得するのを防ぎますか?どんなポインタでも本当にいただければ幸いです。
「一般的な疑似コード」は、CAS(比較およびスワップ)実装の実際のコードではありません。特別なハードウェア命令は、CPUで特別なアトミックハードウェアをアクティブにするために使用されます。たとえば、x86では_LOCK CMPXCHG
_を使用できます( http://en.wikipedia.org/wiki/Compare-and-swap )。
たとえばgccには、ハードウェア固有のアトミックCASを実装する__sync_val_compare_and_swap()
ビルトインがあります。 Paul E. McKenneyによる新鮮な素晴らしい本( 並列プログラミングは難しいですか、そうであれば、それについて何ができますか? 、2014)からのこの操作の説明は、セクション4.3「アトミック操作」です。 、31〜32ページ。
アトミック操作の上に高レベルの同期を構築し、システムをスピンロックから保護し、アクティブなスピニングでCPUサイクルを燃やすことについて詳しく知りたい場合は、Linuxのfutex
メカニズムについて読むことができます。 futexesに関する最初の論文は Futexesはトリッキーです Ulrich Drepper 2011です。もう1つはLWNの記事です http://lwn.net/Articles/360699/ (そして歴史的なものは Fuss、Futexes and Furwocks:Fast Userland Locking in Linux 、2002 )
Ulrichによって記述されたミューテックスロックは、「高速パス」にアトミック操作のみを使用します(ミューテックスがロックされておらず、スレッドをロックしたいのはスレッドだけです)が、ミューテックスがロックされている場合、スレッドはfutex( FUTEX_WAIT ...)(そして、アトミック操作を使用してmutex変数をマークし、ロック解除スレッドに「このmutexで待機している誰かが眠っている」ことを通知します。そのため、ロック解除者はfutex(FUTEX_WAKE、.. 。)
2つのスレッドがロックを取得するのをどのように防ぐのですか? 1つのスレッドが成功すると、*mutex
は1
になるため、他のスレッドのCASは失敗します(期待値0
で呼び出されるため)。ロックは、0
を*mutex
に格納することで解放されます。
これはCASの奇妙な使用法であることに注意してください。これは本質的に必須 ABA違反であるためです。通常は、単純な原子交換を使用します。
while (exchange(mutex, 1) == 1) { /* spin */ }
// critical section
*mutex = 0; // atomically
または、少し洗練されて、どのスレッドがロックを持っているかについての情報を保存したい場合は、atomic-fetch-and-addを使用してトリックを実行できます(たとえば、Linuxカーネルのスピンロックコードを参照)。