こんにちは、プロダクト開発部のちょうです。
結構前に、この記事で、C++11で実験しようと書いていたので、先週ちょっとやってきました。
まずコード(C++11)を出します。
#include <iostream> #include <thread> int data = 0; bool flag = false; void thread1() { data = 100; flag = true; } void thread2() { while(!flag) { } std::cout << data << std::endl; } int main() { std::thread t2(thread2); std::thread t1(thread1); t1.join(); t2.join(); return 0; }
C++のコードを読めない方もいると思いますが、ここですこしコードを説明します。
グローバル変数dataとflagがあります。関数thread1で、dataを100にセットして、flagをtrueにします。関数thread2で、まずflagをチェックして、もしtrueでしたら、dataの内容を標準出力に出します。
関数mainの中では、関数thread1とthread2の内容を別々のスレッドで実行させます(thread2をthread1の前に書くのは問題発生しやすいようにするためです)。
つまりやりたいことはこれです。
コード自体は問題なさそうですが、実際実行したら、どうですか。
実験では
- 普通のパソコン(x86_64)
- Raspberry Pi 3 b+(ARMv7)
を使います。x86_64とARMv7はCPUのアーキテクチャです。ちなみに、ARMプラットホームをテストしたいなら、スマートフォンよりRaspberry Piでやるほうがやすい、安心と思いますのでおすすめです。
パソコンで結果は
100
予想通り。ではRaspberry Piではどうですか。
(プログラムが帰ってこない)
!!! ちょっと予想外のことが起きたかもしれません。ただプログラムがとてもシンプルで、怪しいところがなさそうですが。
ここで悩むより、もう一つのプログラムを動かしましょう。
#include <iostream> #include <thread> #include <atomic> std::atomic_int data{0}; std::atomic_bool flag{false}; void thread1() { data.store(100, std::memory_order_relaxed); flag.store(true, std::memory_order_release); } void thread2() { while(!flag.load(std::memory_order_acquire)) { } std::cout << data.load(std::memory_order_relaxed) << std::endl; } int main() { std::thread t2(thread2); std::thread t1(thread1); t1.join(); t2.join(); return 0; }
コードの内容を気にしないでください。おおまかの内容はプログラム1と同じです。
パソコンで実行すると
100
問題ない、今度Raspberry Piはどうですか。
100
ようやくRaspberry Piでも正しく実行できました。
2つのプログラムで何が違うというと、二番目のプログラムは並列処理を考慮してちゃんと対応していたということです。プログラム1で帰ってこないというのは、thread2でflagをセットしたのを見えなくて無限ループしてしまったようです。まさにタイトル通り「違う値を見た」ということです。このシリーズの最初でCPUの設計はプログラムに影響があると説明したのですが、実験で実際を見れたんです。
ここでもっと原因追及をしたいですが、正直エンジニアとして複数のCPUアーキテクチャを理解する必要がありません。プログラム言語が提供する統一されたMemory Modelを学ぶほうがいいと思います。
最後に
いかがでしょうか。CPUのアーキテクチャが並列処理のプログラムに影響があるかのを体験できましたでしょうか。これからも、並列処理のプログラムを書くとき注意をしましょう。