はじめに
この記事は、個人的に
「今後これ使うだろうな。」「でも多分忘れるだろうな。」
と感じたことを、雑に書いていく記事になります。
間違っている部分、理解が浅い部分等は、指摘して頂けると大変助かります。
本題
今回は、C++のマルチスレッドの基礎的な部分になります。
#include <thread>
#include <iostream>
//加算する回数
const int COUNT_MAX = 10000;
int main(void)
{
int ret = 0;
//count1
for(int c = 0; c < COUNT_MAX;c++)
{
//COUNT_MAX回加算するだけ
++ret;
std::cout << "count1" << ret << std::endl;
}
//count2
for(int c = 0; c < COUNT_MAX;c++)
{
//COUNT_MAX回加算するだけ
++ret;
std::cout << "count2" << ret << std::endl;
}
std::cout << "return" << ret << std::endl
return 0;
}
count1の加算処理が終了した後に
count2の加算処理が実行される...
このような処理をマルチスレッド化していきます。
thread
まず、C++においてマルチスレッドを使用するには
threadのインクルードが必要になります。
そして、マルチスレッドの宣言には
std::threadを使います。
#include <thread>
#include <iostream>
const int COUNT_MAX = 10000;
int main(void)
{
int ret = 0;
//マルチスレッド
//関数オブジェクトを渡す
std::thread thread1([&]()
{
for(int c = 0; c < COUNT_MAX; c++)
{
++ret;
std::cout << "thread1:" << ret << std::endl;
}
});
//マルチスレッド
//関数オブジェクトを渡す
std::thread thread2([&]()
{
for(int c = 0; c < COUNT_MAX; c++)
{
++ret;
std::cout << "thread2:" << ret << std::endl;
}
});
std::cout << "return" << ret << std::endl
return 0;
}
これでマルチスレッドになりました。
std::threadを宣言した際に、ラムダ式を渡していますが
これは、マルチスレッドの内部処理として
関数オブジェクトを渡す必要があるためです。
しかし、いざ実行するとエラーが出てしまいます。
メインスレッドとサブスレッド
マルチスレッドには
メインスレッドとサブスレッドが存在しています。
今回のコードで言うと
メインスレッド : main
サブスレッド : thread1,thread2
となります。
これらのスレッドは別々に動いていて、
メインスレッドの状態に関わらず、各サブスレッドは処理を進め、終了します。
もちろん逆もあり
各サブスレッドが終了していなくても、メインスレッドが終了します。
Join
threadのJoin関数とは
そのスレッドが終了するまで待機させる関数です。
#include <thread>
#include <iostream>
const int COUNT_MAX = 10000;
//メインスレッド
int main(void)
{
int ret = 0;
//サブスレッド1
//関数オブジェクトを渡す
std::thread thread1([&]()
{
for(int c = 0; c < COUNT_MAX; c++)
{
++ret;
std::cout << "thread1:" << ret << std::endl;
}
});
//サブスレッド2
//関数オブジェクトを渡す
std::thread thread2([&]()
{
for(int c = 0; c < COUNT_MAX;c++)
{
++ret;
std::cout << "thread2:" << ret << std::endl;
}
});
//サブスレッド1が終了するまで待機
thread1.join();
//サブスレッド2が終了するまで待機
thread2.join();
std::cout << "return" << ret << std::endl
//メインスレッド終了
return 0;
}
これで、各サブスレッドが終了した後に、メインスレッドを終了することが出来ます。
これで、先程と同じエラーは起こらないはずです。
「Sleep関数を使用し、メインスレッドをブロックする」
という方法も存在します。
次の課題
このコードを実行すると
本来の結果と異なる結果が出てくることがあります。
//本来の結果
return20000
//本来とは異なる結果の例
return19998
本来は
1.変数retを読込
2.加算処理
3.変数retに加算結果を反映
という
123の流れで処理が行われます。
が
マルチスレッドの場合
処理順が、thread1の123の後、thread2の123ではなく
thread1の12→thread2の12→thread1の3→thread2の3
などのようになってしまうことがあります。
この時、変数retの値は
thread1の加算処理が反映されないまま、thread2で読み込まれ
thread1から書き込んだ値は、thread2から書き込まれる値によって
上書きされてしまいます。
この「別スレッドに割り込まれる」現象は、コードの結果から見ることができます。
//正しく増加している
thread2:thread1:18374
18374
thread1:18376
//どちらか片方の結果が消失している
thread1:thread2:19974
19974
thread2:19975
そして、上記の結果を得られる保証もないという恐ろしい状態です。
このように、複数のスレッドから自由に書き込みが出来るため
中身のデータの整合性が取れない状態を非スレッドセーフと呼びます。
この問題の解決のためには
変数ret、または加算処理をスレッドセーフにしなくてはいけません。
この方法を次回に書いていきます。
余談
threadは、英語で「糸」を意味しているらしいです。
シングルスレッドを一本の糸
マルチスレッドを複数の糸の束(枝分かれして、また合流する)
という感じで考えると、何かしっくりくるような気がしますね。
次回の備忘録
std::atomicとかstd::mutex