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

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

新規登録して質問してみよう
ただいま回答率
85.50%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

1752閲覧

std::atomicの間接参照の読み込みについて

alphya

総合スコア124

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2018/08/30 02:15

編集2018/08/30 04:02

###atomicな間接参照の読み込みについて

私は検索しましたが、この話題について書かれている記事をみつけることは難しかったので、質問させていただきました。わからないことは、次の通りです。

std::atomicにポインタを入れて間接参照するときに、そのポインタの中身にアクセスする処理までスレッドセーフになるか。
(18/08/30 12:17 追記) 参照先にアクセスできるのは唯一の、ポインタのatomic変数だけです。

Thanks for any help! :)

cpp

1#include <iostream> 2#include <atomic> 3#include <thread> 4 5struct mystruct 6{ 7 ~mystruct() { value = 0; } 8 int value = 0; 9}; 10 11class myclass 12{ 13 mystruct m_mystruct; 14 std::atomic<mystruct*> m_atomic_mystruct_ptr; 15public: 16 myclass() { m_atomic_mystruct_ptr.store(&m_mystruct); } 17 mystruct & get_mystruct() { return *m_atomic_mystruct_ptr.load(); } 18 void mystruct_worker(std::size_t num) { 19 while(num --> 0) 20 m_atomic_mystruct_ptr.load()->value++; 21 } 22}; 23 24int main() 25{ 26 myclass m; 27 std::thread t(&myclass::mystruct_worker, &m, 10000); 28 std::this_thread::sleep_for(std::chrono::nanoseconds(10)); 29 int i = m.get_mystruct().value; // この処理はスレッドセーフか? 30 std::cout << i << "\n"; 31 t.joinable() ? t.join() : t.detach(); // (13:01追記)単に、t.join()だけでOK! 32}

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

std::atomicにポインタを入れて間接参照するときに、そのポインタの中身にアクセスする処理までスレッドセーフになるか。

いいえ。std::atomic<T*> pが保証するアトミック性(スレッドセーフ性)は、ポインタp自身への書き込み・読み取り操作だけです。ポインタが指す先*pT型オブジェクトのスレッドセーフについては、T型それ自身で対応する必要があります。

質問掲示コードはmy_struct::valueにおいてデータ競合(data race)を引き起こすため、プログラムは未定義動作となります。


題意とは直接関係しませんが、掲示コードの★箇所が気になりました。ここでのtは常にjoinableですから、t.join();とだけ書けば十分かと思います。また仮にt.joinable()がfalseだとすると、t.detach()は例外std::system_errorをスローします(期待する動作ではないですよね?)

c

1std::thread t(&myclass::mystruct_worker, &m, 10000); 2// ... 3t.joinable() ? t.join() : t.detach(); // ★

投稿2018/08/30 03:47

編集2018/08/30 03:52
yohhoy

総合スコア6189

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

alphya

2018/08/30 03:58

詳しくご説明いただき、ありがとうございます!!!
guest

0

ベストアンサー

こんにちは。

そのような使い方を考えたことがないですが、あり得ないです。
atmic変数はatmincテンプレートでラップし、ゴニョゴニョ(詳しくは把握していません)してアトミックにアクセスできるようにします。
ポインタをatmic変数とした場合、そのポインタ自身はきちんとラップされているので定義通りアトミックにアクセスできるよう保証されています。しかし、そのポイント先は当然ですがラップされておらず丸裸です。ですので「ゴニョゴニョ」する術がありませんから、アトミックにアクセス出来ることは保証されません。

投稿2018/08/30 02:45

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

alphya

2018/08/30 03:23 編集

回答していただきありがとうございます!! すみません、質問文に不備がありました...... 知りたいのは、ポインタのatmic変数、上のコードの例なら` myclass::m_atomic_mystruct_ptr`でしかアトミックにしたいオブジェクトにアクセスできないような実装になっているときです。 よろしくお願いします!!
Chironian

2018/08/30 03:27

return *m_atomic_mystruct_ptr.load();は、まず、m_atomic_mystruct_ptr.load()により、mystruct*を得ることが出来ます。入手するのはポインタです。この操作はアトミックであることが保証されます。 次に、*単項演算子により、コンパイラは参照として解釈するようになります。 最後に、returnでその参照が返却されます。get_mystruct()の戻り値はmystructへの参照ですから、このタイミングで、恐らくmystruct*ポインタのアドレスがコピーされると思いますが、この時点でatmicは既に剥がれていますので、この操作はアトミックであることは保証されません。(とはいえアトミックにアクセスされる処理系がほとんだろうとは思います。もしも32bit CPUで64bit環境をシミュレートするような処理系があるとアトミックにはならないでしょう。) 更に、get_mystruct()が返却したものはmystructオブジェクトへの参照ですが、これもatmicでラップされていませんので保護する術がありませんから、mystructオブジェクトへのアクセスもアトミックにアクセスされることは保証されません。
alphya

2018/08/30 03:43

ご回答ありがとうございます!! つまり、std::atomicはポインタを入れると、オブジェクトへのアクセスはアトミックでなくなるということでしょうか?
Chironian

2018/08/30 04:22

atomicに入れたものだけが保護されます。atomicの外にあるものは保護されません。 ポインタはポインタ変数であり、その値はポイント先のオブジェクトがあるメモリ・アドレスです。 このポインタ変数アクセスがアトミックであることが保証されます。ポインタ変数が指す先にあるオブジェクトはatomicの外にありますから、保護されません。 atomicは便利な機能というよりはCPUの機能をマルチプラットフォームで使おうとするものですから制約も厳しいのです。
alphya

2018/08/30 04:49

atomicは意外と厳しいのですね...... コメントによる質問にお付き合いいただき、ありがとうございました! All your help has been great! :)
yohhoy

2018/08/31 02:40

少しだけ気になったのでコメント:atomic<T>を特殊なものと考えすぎていませんか?意味上は atomic<T>=複数スレッドから同時Read/WriteできるT型変数 というだけです。 C++標準ライブラリ仕様としても、atomic<T>の内部を単純な { T型の変数1個+Mutex1個 } で実装しても良いことになっています。実際にはCPU命令を直接利用した、Mutexに頼らないatomicアクセスが提供されることが多いです。 atomic<T*>の場合は、Mutexで保護されたポインタ値(T*) に相当します。つまり、ポインタ値(T*)自身のatomic性と、そのポインタが指す先のオブジェクト(T)のatomic性には何の関係性もありません。
alphya

2018/08/31 08:59 編集

@yohhoy さん、コメントありがとうございます。 はい!お二方の回答より、そのように理解しています!本来ならyohhoyさんの投稿を、この投稿を見に来た人のためにもベストアンサーにするべきでしたね。ですが、もうつけてしまっていたのでそれを外すのもどうかと思い、またコメント欄にも同じ答えが書いてあるので、こちらのほうにベストアンサーをつけさせていただきました! std::atomicが"厳しい"といったのは、あるクラスをstd::atomicに入れてスレッドセーフにするためには、トリビアルコピー可能でなくてはならず、メンバには間接参照するためのポインタも入れられず(std::string::c_str()はconstメンバ関数(標準ライブラリのconstメンバ関数は、スレッドセーフが保証されている)なので、文字列については大丈夫そうですね!)、実装しようとしているクラスはどうやっても入れることができなかったからです。 std::mutexを使ってスレッドセーフにしようとしても、かなり呼び出す回数が多くパフォーマンスに直接影響するようなクラスだったので、std::atomicで間接参照できればいいなと思っていたのですが、やっぱりだめでしたね......。もし何かいい実装が見つかったら、教えていただければ幸いです。
yohhoy

2018/08/31 06:20 編集

std::atomic<T> はプリミティブ型(≒CPUネイティブで扱える型)用途ですので、自作クラスの格納には不適ですね。ちなみに、TriviallyCopyableを満たせないので std::atomic<std::string> はNGです。 外部ライブラリ(Boost)でもよければ boost::synchronized_value<T> が汎用の排他機構付きT型変数のように使えます。こちらは常に T型変数 + Mutex1個 で実現され、安全なアクセス機構を提供します。 Mutexによるパフォーマンス低下が"本当に"問題になる場合、そもそものアルゴリズムやデータ構造、外部仕様や並列処理設計を見直すべきと思います(もちろん、これ以上どうがんばっても無理というラインはあります)。Mutexは「常に最新の状況を共有すべきデータ」を保護するものですが、この条件が本当に必要なのかという切り口から再考するのがよいかもしれませんね。"常に"→まとめて処理できない?"最新"→古い値が見えたら?"共有"→複製分離できない? など。
alphya

2018/08/31 08:37

@yohhoy さん、返信ありがとうございます! ご教授いただいたboost::synchronized_value<T>を使って、なんとか実装することができました!(yohhoyさんの日記を参考にさせていただきました(≧∇≦)) 設計に関しては、後日見直していきたいと思います。 それにしても、std::atomicは文字列を扱えないんですか......。なかなか厳しいですね...... この度は本当にありがとうございました!!! また、機会があればよろしくお願いします!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問