まえがき
先日のCode Jamで並列処理を行えば
ゴリ押しで解ける問題が出題された。
本番中ゴリ押し解を思いつくには思いついたのだが、C++でマルチスレッドの処理を書いたことが無くて、ごにょごにょやってるうちにタイムアップとなってしまった。
せっかくなので、C++でマルチスレッドのプログラムの書き方を勉強してみた。後で見返した時にすぐ思い出すように簡単にまとめておく。
- マルチスレッド版Hello, World
- 子スレッドに引数を渡す
- mutexを使ったロック
- 子スレッドから結果を受け取る
- 子スレッドに後から引数を渡す
1. マルチスレッド版Hello, World
threadクラスのインスタンスを作成すると、スレッドが作成される。
スレッド化したい処理はコンストラクタで渡す。
コンストラクタに渡す処理は、関数でもファンクタでもラムダ式でもよい。
threadインスタンスのjoin()を呼ぶとスレッドの完了を待つ。
detachを呼ぶとスレッドの完了を待たずにmainは終了する。
#include <iostream>
#include <thread>
using namespace std;
int main(int argc, char **argv) {
thread t1([]() { cout << "Hello, World!" << endl; });
// do other tasks
t1.join(); // wait t1 to finish
//t1.detach(); // do not wait t1 to finish
return 0;
}
2. 子スレッドに引数を渡す
スレッド化したい処理に引数がある場合は、threadのコンストラクタの第2引数以降で指定する。
引数を参照で渡したい場合は、refを使って参照ラッパーを渡す。
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
void sayHello(vector<string> &names) {
for (auto &x : names)
cout << "Hello, "<< x << "." << endl;
}
int main(int argc, char **argv) {
vector<string> names{
"taro",
"hanako",
"jiro",
"mika"
};
thread t1(sayHello, ref(names));
t1.join();
return 0;
}
3. mutexを使ったロック
複数スレッドで共有するリソースを使う場合はmutexで排他制御を行う。
mutexクラスのlock、unlockメソッドを直接使うのは非推奨。RAIIでmutexの管理を行う標準ラッパークラスが提供されているのでそれを使う。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
using namespace std;
mutex mtx_stdout;
void work(int t) {
{
lock_guard<mutex> guard(mtx_stdout); // RAII
cout << "work: " << t << " begin." << endl;
}
//....
this_thread::sleep_for (chrono::seconds(3));
{
lock_guard<mutex> guard(mtx_stdout); // RAII
cout << "work: " << t << " end." << endl;
}
}
int main(int argc, char **argv) {
vector<thread> threads(10);
for (int i = 0; i < 10; i++)
threads[i] = thread(work, i+1);
for (auto &x : threads)
x.join();
return 0;
}
より柔軟な制御を行いたい場合は、unique_lockを使うという選択肢もある。
void work(int t) {
unique_lock<mutex> locker(mtx_stdout, defer_lock);
locker.lock();
cout << "work: " << t << " begin." << endl;
locker.unlock();
//....
this_thread::sleep_for (chrono::seconds(3));
locker.lock();
cout << "work: " << t << " end." << endl;
locker.unlock();
}
5. 子スレッドから結果を受け取る
子スレッドから処理結果を受け取りたい場合は、futureを使う。
asyncの第一引数にlaunch::asyncを指定することで別スレッドで処理を開始出来る。
第一引数にlaunch::deferredを指定した場合は、処理が結果取得時まで遅延される。(※別スレッドは作成されないことに注意)
#include <future>
#include <vector>
#include <iostream>
using namespace std;
bool isPrime(long long n) {
if (n < 2)
return false;
for (long long i = 2; i * i <= n; i++)
if (n % i == 0)
return false;
return true;
}
int main(int argc, char **argv) {
future<bool> fut = async(launch::async, isPrime, 1000000007);
cout << fut.get() << endl;
return 0;
}
6. 子スレッドに後から引数を渡す
スレッド実行時に引数の値が分かっておらず、後から非同期で子スレッドに引数を渡したい場合はpromiseを使う。
#include <future>
#include <vector>
#include <iostream>
using namespace std;
bool test(int x, future<int> &f) {
return x < f.get();
}
int main(int argc, char **argv) {
promise<int> p;
future<int> f = p.get_future();
future<bool> fut = async(launch::async, test, 10, ref(f));
p.set_value(12);
cout << fut.get() << endl;
return 0;
}