まえがき
先日の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; }
0 件のコメント:
コメントを投稿