質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.81%

ミューテックスとアトミック処理について

解決済

回答 6

投稿 編集

  • 評価
  • クリップ 3
  • VIEW 5,726

strike1217

score 585

主にアトミック処理に関する質問です。
いくつか不明な点があります。

アトミック処理とミューテックスの違いについて確認しておきます。
mutex と atomic の違いは何か、どちらが良いか
アトミック操作

アトミック操作の長所は、ロックに比べて比較的処理が速く、デッドロックやコンボイなどを回避できる点にあります。 短所は、限られた操作しか行うことができず、より複雑な操作を効率良く総合的に扱うには不十分なことです。

次に、ロックフリーについて・・・
C++ストラウストラップ氏の本の中では以下のようにあります。

ロックフリープログラミングは明示的なロックを使わずに並行プログラムを開発するためのの一連の技法だ。
明示的なロックの代わりに使うのが、小規模オブジェクトに対するデータ競合を防ぐための(ハードウェアが直接サポートする)基礎的な演算である。データ競合が発生しない基礎的な演算は、一般的にアトミック処理と呼ばれる。
・・・
ロックフリー技法を用いると、ロック技法を用いた場合よりも大幅に速度が向上することもある。
標準アトミック型と処理は、従来方式のロックフリーなコードに代わる、可搬性を持った代替手法を提供する。

[疑問1]
アトミック処理とは、ミューテックスと同じように排他的処理を提供するが、ミューテックスとは対照的にロックを使用しない。
つまり、アトミック処理 = ロックフリー 完全に同値なものだと解釈してよろしいのでしょうか?
アトミックとミューテックスを併用したプログラミングスタイルというものも存在するのでしょうか?
また、アトミック処理以外にもロックフリーを実現することは可能なのでしょうか??

[疑問2]
アトミック処理は、ロックを使用しないのに、どうやって排他的制御を実現しているのでしょうか??
自分の予想なのですが・・・これってもしかしてCPUにアトミック専用命令みたいなものが用意されている?・・・のでしょうか? EX : add命令 -> atomic.add命令 みたいな感じですかね。


ここからvolatileが出てきます。

C++ストラウストラップ氏の本から以下抜粋です。

アトミック処理は、メモリ状態が、指定したメモリオーダの要求通りとなることを保証する。
(メモリオーダーとは)コンパイラやオプティマイザはプログラムの実行速度向上を目的に、命令の順序を並べ替えることがある。
・・・ 
もっとも単純なメモリオーダーは、逐次一貫と呼ばれるものだ。逐次一貫メモリモデルでは、すべてのスレッドは、実行されたすべての処理の結果が同じ順序として見える。その順序は、単一スレッドで命令を逐次実行した場合の順序である。
スレッドが命令の実行順序を変更することはあり得るが、他のスレッドが変数を参照する時点では、それまで実行された処理、及び(その結果である)メモリロケーションの値が的確に定義された状態であることが保証されて、全スレッドから同じものが見える。メモリロケーションに一貫した観点を強制した上で値を”見る”ことをアトミック処理と呼ぶ。

ふぅーーーー。ちょっと長いですね。
要は、命令順序は変更されることは、あっても変数内の値は、的確に定義された状態であることが保証されているということですよね。

では、ここで別の書籍のサンプルコードを見てみます。

volatile std::atomic_flag shared_lock;

void thread_function(int id){
   while(shared_lock.test_and_set())
      sleep(1);
   shared_data = id;
   std::cout << "Thread " << id << " set shared value to " << shared_data << std::endl;
   usleep(id * 100);
   std::cout << "Thread " << id << " has shared value as " << shared_data << std::endl;
   shared_lock.clear();
}


ん??
volatileが出てきています。
volatileが必要な場面を見つけ出す

[疑問3]
このvolatileはどのような効果を持っているのでしょうか?
命令順序は変更されることは、あっても変数内の値は、的確に定義された状態であることはアトミックによって保証されているはずですよね。
これって、volatileの効果とかぶっていませんか??

volatile変数は「コンパイラからは検知できない任意タイミングで更新される可能性のある変数」
スレッドには当てはまらない。はずです。
となると、このvolatileは命令の実行順序と関係がありそうですね。

アトミックはvolatileの性質を含んでいるのでしょうか??

[疑問4]
アトミックとは別にvolatileによく似ているのが、メモリフェンス(メモリバリア)の存在です。

CPUやコンパイラは、メモリフェンスを越えて読み込みと書き込みの操作の順序を入れ替えることができない。

volatile宣言がされている場合、コンパイラはそのデータが置かれたメモリに対する読み書きの順序を変更できない。しかし、これらの読み書きと、他のメモリに対する読み書きとの順序を変更してもよい。

ん〜〜??
なんか言っている事が同じ気がするんですが・・・
volatileとメモリバリアの違いがハッキリしません。

アトミック処理は、メモリ状態が指定したメモリオーダーの要求通りとなる事を保証する。
デフォルトのメモリオーダーは、memory_order_seq_cstである。

int x = 0, y = 0, r1 = 0, r2 = 0;

// スレッド1
x = 1;
r1 = y;

// スレッド2
y = 1;
r2 = x;

プログラム終了時、r1とr2の値はどちらも0になる可能性がある。この状況が発生するのは、スレッド2が1を代入する前にスレッド1がyを読み込んでしまう場合、もしくはスレッド2はyに1を代入したが、値がキャッシュされ、スレッド1がyを読む際に値が反映されていない場合がある。
メモリバリアはこれらのデータ競合を緩和する低レベルな方法である。
次のコードはメモリフェンスを追加している。

int x = 0, y = 0, r1 = 0, r2 = 0;

//スレッド1
x = 1;
atomic_thread_fence(memory_order_seq_cst);
r1 = y;

//スレッド2
y = 1;
atomic_thread_fence(memory_order_seq_cst);
r2 = x;


これ、volatileではダメなんでしょうか?

[疑問5]
疑問2と似ているのですが・・・このメモリバリアと呼ばれるものはどうやって実現しているのでしょうか?
これも、CPUにメモリバリア専用命令みたいなのが用意されているのでしょうか?
メモリバリアの atomic_thread_fenceの名前でatomicが付いているので、そうなのかなぁ〜〜という推測です。

疑問3と4は、アトミックとvolatileとメモリバリアの違いについての疑問です。
わかる方お願いします。

環境は、g++ Linux です。

[追記]
疑問3については、volatileが必要な場面を見つけ出すの中でChironianさんが触れていることに非常に近いですね。

なるほど。そう言えば、関数呼び出しを挟めば最適化が抑止されたことがありました。そのように規定されているのですね!(lock()が同期用のロックかどうかコンパイラは把握できない筈ですし)
アトミック変数はどうでしょう? 必要に応じて暗黙的にvolatileになるのでしょうか?

アトミック変数がvolatileの性質を含んでいるような気がしますが・・・
質問中のコードにはなぜvolatileが登場しているんでしょうかね・・・

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 6

checkベストアンサー

+5

[疑問1]
つまり、アトミック処理 = ロックフリー 完全に同値なものだと解釈してよろしいのでしょうか?

違います。用語を混同しています。「アトミック処理」というのは、日本語では「不可分操作」と訳されていますが、要するに別スレッドからの干渉を受けずに一連の処理を実行することを指す用語です。そして、その実現方法の一つがミューテックスによるロック(排他制御)であり、そのようなロックの仕組みを使わずに、つまり、スレッドの「待ち状態」を作らずにCPU命令等のハード的な仕組みでアトミック処理を行うことをロックフリーと呼びます。

[疑問2]
アトミック処理は、ロックを使用しないのに、どうやって排他的制御を実現しているのでしょうか??

用語を混同しているので意味不明な質問になってしまっています。
「ロックフリーは、ロック(排他制御)をしないのに、どうやってアトミック処理を実現しているのでしょうか??」という質問だと解釈して答えます。

自分の予想なのですが・・・これってもしかしてCPUにアトミック専用命令みたいなものが用意されている?・・・のでしょうか?

x86だと、レジスターのアラインメントに合わせたメモリアドレスに対する読み書きはアトミック実行が保証されていますし、ハード的にメモリバスをロックする仕組みも備わっており、いくつかの演算命令はLOCKプリフィックス付けることでアトミック実行になります。また、XCHG命令はそれ単体でアトミック実行を保証しています。
それらの機能をC/C++から簡単に利用するために、Windows SDKはInterlockedAddなどのアトミック演算関数を提供していますし、Linux等でも<intrin.h>ヘッダーをインクルードすれば[訂正]gcc独自の__sync_fetch_and系各種アトミック演算関数が利用できます。C++で<atomic>が実装される前は、それらの関数を使ってアトミック演算をしておりました。

[疑問3]
このvolatileはどのような効果を持っているのでしょうか?

最適化の抑止です。atomic_flagの仕様が判らないので正確には判りませんが、最適化されると著者が意図しない動作をすると考えたのでしょう。

となると、このvolatileは命令の実行順序と関係がありそうですね。 
アトミックはvolatileの性質を含んでいるのでしょうか??

volatileは最適化しないことで結果的に書いたとおりに実行されることになりますが、それとアトミックとはまったく関係ありません。

[疑問4]
volatileとメモリバリアの違いがハッキリしません。

volatileはコンパイラの最適化抑制による順序付けであり、メモリバリアはCPUのアウトオブオーダー実行における順序付けです。メモリバリアには専用のCPU命令等が必要ですが、volatileはそんなことまではしてくれません。

[疑問5]
これも、CPUにメモリバリア専用命令みたいなのが用意されているのでしょうか?

x86だと、SFENCE/LFENCE/MFENCEという命令があります。あとは、特定の領域に対してオーダリングの利き具合を調整する機能もあるようです。
まぁ、その辺を直接いじるのは、OS開発者かコンパイラー開発者ぐらいなものでしょう。


[追記]
アトミック変数がvolatileの性質を含んでいるような気がしますが・・・

ここでいうアトミック変数が<atomic>で定義されるatomicクラスのオブジェクトのことであれば、volatileの性質を含んでいるというよりは、クラスの実装においてvolatileキーワードが各所で使われていて最適化による順序変更やコード削除を抑止する設計になっているので、いちいち変数定義時に付ける必要はないということですね。

質問中のコードにはなぜvolatileが登場しているんでしょうかね・・・

atomic_flagクラスのことでしょうか。著者が独自に作ったクラスだと思いますが、おそらくクラスの実装で適切にvolatileを使っていないので、オブジェクトに対して明示的にvolatileを付けないと意図しない動きになってしまうのでしょう。


[訂正]
Linuxでは<intrin.h>をインクルードすればアトミック演算関数が使えると書きましたが勘違いでした。別の要件で<intrin.h>を使っていててっきり同じものだと勘違いしていました。しかもintrin.hではなくx86intrin.hでしたし……。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/25 20:37

    あ!以下を参考にしました。

    https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%90%E3%83%AA%E3%82%A2#%E3%82%A2%E3%82%A6%E3%83%88%E3%82%AA%E3%83%96%E3%82%AA%E3%83%BC%E3%83%80%E3%83%BC%E5%AE%9F%E8%A1%8C%E3%81%A8%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%81%AB%E3%82%88%E3%82%8B%E5%91%BD%E4%BB%A4%E9%A0%86%E5%BA%8F%E3%81%AE%E6%9C%80%E9%81%A9%E5%8C%96

    > メモリバリア命令はハードウェアレベルの命令並べ替えに対するものである。コンパイラも最適化処理の一環として命令の並べ替えをすることがある。

    つまり、実行時とコンパイル時の両方で命令順序が変更されないようにしたい場合は、volatileとメモリバリアの両方を併用しなくてはならない。
    ということですね。

    キャンセル

  • 2018/12/25 20:50

    > つまり、実行時とコンパイル時の両方で命令順序が変更されないようにしたい場合は、volatileとメモリバリアの両方を併用しなくてはならない。

    そういうことになりますね。volatileはソースコードに書かれた通りに(その順番で)機械語に変換されます。ただし、その機械語をCPUが実行する際、必ずしも順番通りに実行されるとは限らないため、それでは困る場合にメモリバリアで順序の入れ替えを抑制させます。

    キャンセル

  • 2018/12/25 20:51

    なるほど!
    理解しました。

    キャンセル

+3

こんにちは。

アトミック処理とは、ミュータックスと同じように排他的処理を提供するが、ミューテックスとは対象的にロックを使用しない。
つまり、アトミック処理 = ロックフリー 完全に同値なものだと解釈してよろしいのでしょうか?

std::is_lock_freeがあるということは、std::atomicはロックフリーな場合とそうでない場合があるようです。

アトミック処理は、ロックを使用しないのに、どうやって排他的制御を実現しているのでしょうか??
自分の予想なのですが・・・これってもしかしてCPUにアトミック専用命令みたいなものが用意されている?・・・のでしょうか?

具体的な命令は把握していませんが、CPUやバスコントローラ等のハードウェアで実装されているようです。当該処理を行っている間、バスを開放しなければマルチCPU環境でも整合性を保てますので。

このvolatileはどのような効果を持っているのでしょうか?

volatileは単なる最適化の抑止に過ぎません。バスの専有とは無関係です。

アトミックとは別にvolatileによく似ているのが、メモリフェンス(メモリバリア)の存在です。

メモリフェンスはstd::atomicと一緒に出てくる概念ですね。複数のCPU間でのアクセス上の整合性を維持するための機能のようです。
たぶん、マルチCPUシステム向けのバス設計やパイプライン処理、アウト・オブ・オーダー実行、メモリ・キャッシュ等に関するそれなりの知識がないと理解するのは超ハードなのかもと予想しています。

私は理解できなかったので、メモリフェンスに関して最適化はしない方針です。(デフォルトの一番効率が悪いけど確実な設定で使います。)よほどガチガチなものを作るのでない限り、性能劣化は事実上無視できるだろうと思います。

疑問2と似ているのですが・・・このメモリバリアと呼ばれるものはどうやって実現しているのでしょうか?
これも、CPUにメモリバリア専用命令みたいなのが用意されているのでしょうか?

アウトオブ・オーダー実行やキャッシュがある環境でマルチCPUからのアクセスをどうやって調停しているのか見当も付かないです。たぶん、ハードでうまいことやっているのだろうと思っています。

yohhoyさんの メモリモデル?なにそれ?おいしいの? が分かりやすいのです。私はこれを見て、とても私には理解できない話であることと、理解する必要まではないことを理解できました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/24 23:20

    ちょっとリンク先を見てみます。

    キャンセル

+2

[1] アトミック処理 = ロックフリー 完全に同値なものだと解釈してよろしいのでしょうか?

違います。lock-freeはアトミックの特殊な場合。
lock-freeが適用できるケースには限りがあります。

[2] アトミック処理は、ロックを使用しないのに、どうやって排他的制御を実現しているのでしょうか??
自分の予想なのですが・・・これってもしかしてCPUにアトミック専用命令みたいなものが用意されている?・・・のでしょうか?

CAS(Compare And Swap)ってCPU命令があります。これは1命令なので他のスレッドに割り込まれることがありません。

残る3つは...誰ぞお願い。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/24 22:43

    > 違います。lock-freeはアトミックの特殊な場合。
    なるほど!!
    全くイコールなものではないわけですね。

    > CAS(Compare And Swap)ってCPU命令があります。
    ほぉぉ・・・そんな命令が!!
    調べてみます。

    キャンセル

+2

アトミックとミューテックスを併用したプログラミングスタイルというものも存在するのでしょうか?

Linux の pthread mutex と Windows のクリティカルセクションオブジェクト(EnterCriticalSection等で使用されるオブジェクト)は規定の回数はアトミックロックによりロックを確保(スピンロックとか呼ばれる)しようとして、アトミックロックに失敗した場合のみカーネルオブジェクトの mutex によりロックを確保します。

質問の回答としては OS レベルの API ではパフォーマンスのためにそのような実装になっているものがあるというところですかね。

解説サイトを探してみたらありましたね。
http://www.tsoftware.jp/nptl/
このサイトによると pthread mutex の スピンロックは Posix 非標準とのこと・・・。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/25 12:23

    おお!なるほど!

    > アトミックロックによりロックを確保(スピンロックとか呼ばれる)しようとして、アトミックロックに失敗した場合のみカーネルオブジェクトの mutex によりロックを確保します。

    アトミックとミューテックスを併用しているわけですね!

    キャンセル

+1

アトミック処理ってのは、いわば1命令で終了する処理、ですね、
割り込み処理がどうかかろうが処理結果には影響しない処理です

疑問3:
volatileは、あらゆる最適化をされないことを保証するキーワードです
あくまで最適化されない、であって、排他であるかどうかは考慮されてません。
が、結果的に排他である場合が多いので、これまでそういう場面において多用されて来ましたが、近年、マルチコア状況下や投機実行動作時には排他されない場合がありえるということで、こういう用途にvolatileは適さないということが言われてきました。

疑問4:
前にも言ってますが、volatileは排他目的のキーワードではありません。
その性質上、結果的に排他している、というだけのはなしです。
これに対し、メモリバリアってのは、あらゆる場面において排他されることが保証されている処理となります

疑問5:
メモリバリアはCPUの専用の仕組み、あるいは専用の命令として組み込まれてます
volatileは、その性質上、処理的に割り込まれないようにしている、というのに対し、メモリバリアはハードウエア的に割り込まれない処理を実装してます

#まあ、専用の命令がないけど、内部的に割り込まれないような実装にしてる場合もありますが

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

atomic代替のために、volatileを用いてならない。
また、volatileの代替としてatomicを用いてはならない。

ということは良いですよね。
volatile std::atomic_flag
std::atomic_flag
thisポインタにvolatileを付けているメソッドがありました。
おお!こんなところで出てくるとは!!

bool test_and_set(memory_order order = memory_order_seq_cst) volatile noexcept;
bool test_and_set(memory_order order = memory_order_seq_cst) noexcept;

アトミック処理 = ロックフリーではなく。
アトミックとミューテックスを併用したスタイルも存在するということですね。
このスタイルは、ロックフリーに該当しない・・・ですね。

ロックフリープログラミングの手法を使うときは ABA 問題を避ける

ロックフリープログラミングの手法では、CAS (compare and swap) や LL/SC (load linked/store conditional) といった特別なアトミックプロセッサ命令、あるいは C の標準関数である atomic_compare_exchange を使う必要がある

これを見る限りだと、アトミック処理を使用しないで、ロックフリーを実現するのは不可能・・・ということですかね・・・


[追記]
メモリバリアには、アウトオブオーダー実行の命令順序の変更ができないようにします。
コンパイル時の命令順序変更をできないようにするには、メモリバリアでも可能なはずです。
(コンパイル時に変更できちゃったら、元も子もないような・・・)
つまり、メモリバリアは、volatileの代替になる場合がある。ということですよね。

std::atomicには、逐次一貫のため、内部にメモリバリアを用いていますね。
つまり、std::atomicは、volatileの一部の機能を含んでいることになりますよね。

ん???
なんか・・・変!
std::atomicを、命令実行順序の変更をさせないという目的においては、volatileの代替として利用することができることになりますね。

実際、Effective modern C++ にはそれに似たような事が書かれていますね。

データが置かれたメモリに対する読み書きの順序 と 命令の実行順序 は厳密に区別されなくてはならない・・・ということでしょうか?


[追記2]
追記の部分は完全に不適切です。
メモリバリア

メモリバリア命令はハードウェアレベルの命令並べ替えに対するものである。コンパイラも最適化処理の一環として命令の並べ替えをすることがある。

えーー。つまりvolatileの代替にメモリバリアを使用することはできない。ということですね。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/25 15:44 編集

    (不適切なの削除)

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.81%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る