はじめに
私は先日TeraTailに質問を投稿して、有用そうな情報を得たので一応投稿しておきます。
std::atomic
を使う際の注意点の一つ
std::atomic
に間接参照用のポインタを入れても、参照を解決するのはスレッドセーフになりませんよ!
以下のコードを見てください。
# include <iostream>
# include <atomic>
# include <thread>
struct mystruct
{
~mystruct() { value = 0; }
int value = 0;
};
class myclass
{
mystruct m_mystruct;
std::atomic<mystruct*> m_atomic_mystruct_ptr;
public:
myclass() { m_atomic_mystruct_ptr.store(&m_mystruct); }
mystruct & get_mystruct() { return *m_atomic_mystruct_ptr.load(); }
void mystruct_worker(std::size_t num) {
while(num --> 0)
m_atomic_mystruct_ptr.load()->value++;
}
};
int main()
{
myclass m;
std::thread t(&myclass::mystruct_worker, &m, 10000);
std::this_thread::sleep_for(std::chrono::nanoseconds(100));
int i = m.get_mystruct().value; // ← 未定義動作!!
std::cout << i << "\n";
t.join();
}
このように、std::atomic
にポインタを入れて使用しようとしたとします。しかし、スレッドセーフにアクセスできるのはポインタの値を読み出す処理だけ、つまりメモリのアドレスを取得する処理だけで、*間接参照演算子を使った代入などの、そのアドレスのさす先の値を変更したり読み出したりする処理はスレッドセーフになっていないです。
もちろん、std::atomic<char*>
のようにして"文字列"をスレッドセーフに扱うのも未定義です。std::atomic
に格納しているのは、あくまでchar型のポインタ(=メモリアドレス)です。
std::atomic
に入れたいクラスのメンバに、間接参照用のポインタが存在するかもしれないことにも注意が必要かもですよ!
余談
おそらく、このような処理をしたい方は、
-
std::atomic
にはトリビアルコピー可能な型しか入れられないので、何とかポインタで代用できないかと思った -
std::mutex
を使った実装は、スレッドセーフにしたい処理の全部の最初に実装するのが正直大変だし、速度も遅くなりそう
などの理由があったと思います。
そんな方には、boost::synchronized_value<T>
があります。
内部的にmutexを使用しているので、速度に関してはmutexと同じくらいになりますが、こちらは特に型に制約はなく、std::atomic
と同じような文脈で使えます。
競合も何もない簡単なベンチマークを行ってみたところ、処理時間は、std::atomic
< `std::mutex` ≒ `boost::synchronized_value`のようになりました。コードは、下にリンクを貼っておきます。
参照
boost::synchronized_value<T>
の使い方
簡単な速度のベンチマーク ← エラーの山は無視してください。コンパイルは通るのですが......
Teratailに投稿した質問