スレッドによる並列化
それではスレッドによる並列化の例として、実際に素数の判定を行うプログラムを並列化して行きます。
素数の判定方法にも色々なやり方があるとは思いますが、ここでは単純に3以上√n以下の奇数で、nが割りきれなかったら素数と判断することにします。これを3から99999までのすべての奇数に対して判断を行います。
このプログラムでは、1つ1つの奇数について素数であるかどうか判断するというタスクが複数ある、というタスク分割の考え方ができます。各タスクには依存関係がないのでタスク並列によって並列化したのが次のようなコードです。
#ifndef SHARED_QUEUE_HXX #define SHARED_QUEUE_HXX #include <queue> #include <boost/thread.hpp> template < typename T > class SharedQueue { private: std::queue < T > m_que; boost::mutex m_mtx; boost::condition_variable m_not_empty; public: void enqueue(const T & x) { { boost::lock_guard < boost::mutex > lock(m_mtx); m_que.push(x); } m_not_empty.notify_one(); } T dequeue(void) { boost::unique_lock < boost::mutex > lock(m_mtx); while (m_que.empty()) m_not_empty.wait(lock); T x = m_que.front(); m_que.pop(); return x; } }; #endif /* SHARED_QUEUE_HXX */
//#include <iostream> //#include <iterator> #include <vector> #include <cmath> #include <boost/thread.hpp> #include <boost/progress.hpp> #include "shared_queue.hxx" class PrimeCalc { private: std::vector<int> & m_prime_vector; SharedQueue<int> & m_shared_queue; public: PrimeCalc(std::vector<int> & prime_vector, SharedQueue<int> & shared_queue) : m_prime_vector(prime_vector), m_shared_queue(shared_queue) {} void operator () (void) { while (true) { int n = m_shared_queue.dequeue(); if (0 == n) break; bool prime_flag = true; int square_root = static_cast <int> (sqrt(n)); for (int j = 3; j <= square_root; j += 2) { if (0 == (n % j)) { prime_flag = false; break; } } if (true == prime_flag) { m_prime_vector.push_back(n); } } } }; int main(void) { std::vector<int> v0; /* prime vector */ std::vector<int> v1; /* prime vector */ SharedQueue<int> sq; for (int i = 3; i <= 999999; i += 2) { sq.enqueue(i); } sq.enqueue(0); sq.enqueue(0); PrimeCalc pc0(v0, sq); PrimeCalc pc1(v1, sq); { boost::progress_timer pt; boost::thread th0(pc0); boost::thread th1(pc1); th0.join(); th1.join(); } //std::copy(v0.begin(), v0.end(), std::ostream_iterator<int>(std::cout, " ")); //std::copy(v1.begin(), v1.end(), std::ostream_iterator<int>(std::cout, " ")); //std::cout << std::endl; return 0; }
それぞれshared_queue.hxxとprime_task_parallel.cxxという名前でファイルに保存し、コンパイルします。
$ g++ prime_task_parallel.cxx -o prime_task_parallel.elf -lboost_thread
実行してみましょう。
$ ./prime_task_parallel.elf 1.15 s
このプログラムの解説です。
main関数では、まず素数を判定した結果を保存するために、prime_task_parallel.cxxの43行目で標準ライブラリのvectorクラスの変数を用意し、タスクが処理するべきデータを保存するために45行目でキューを用意します。
ここで使用するキューは、標準ライブラリのqueueクラスを使い、それに排他制御を追加したものをshared_queue.hxxで独自にSharedQueueクラスとして定義しています。
次に、47行目のforループで、そのキューに対して素数の判定に使用する奇数を追加します。
54行目では、PrimeCalcクラスの変数を、結果を保存するvectorとタスクを受け取るキューを引数にして定義します。実際の素数の判定はこのPrimeCalcクラスによって行われます。そして、60行目でboost::threadクラスの変数にPrimeCalcクラスの変数を渡して定義することで、新たなスレッドを生成して並列実行します。
16行目のPrimeCalcクラスのoperater ()関数では、キューから1つずつ奇数を取り出して素数かどうか判定し、素数だった場合には結果を保存するためのvectorに詰めていきます。
![]() |
2/3 |
![]() |
Index | |
タスク並列とデータ並列の違い | |
Page1 マルチスレッドプログラムの実際 分割と依存関係 並列化のためのアルゴリズム |
|
![]() |
Page2 スレッドによる並列化 |
Page3 データ並列で実装してみる 処理の流れを比べてみよう |
![]() |
Think Parallelで行こう! |
- プログラムの実行はどのようにして行われるのか、Linuxカーネルのコードから探る (2017/7/20)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。最終回は、Linuxカーネルの中では、プログラムの起動時にはどのような処理が行われているのかを探る - エンジニアならC言語プログラムの終わりに呼び出されるexit()の中身分かってますよね? (2017/7/13)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、プログラムの終わりに呼び出されるexit()の中身を探る - VBAにおけるFileDialog操作の基本&ドライブの空き容量、ファイルのサイズやタイムスタンプの取得方法 (2017/7/10)
指定したドライブの空き容量、ファイルのタイムスタンプや属性を取得する方法、FileDialog/エクスプローラー操作の基本を紹介します - さらば残業! 面倒くさいエクセル業務を楽にする「Excel VBA」とは (2017/7/6)
日頃発生する“面倒くさい業務”。簡単なプログラミングで効率化できる可能性がある。本稿では、業務で使うことが多い「Microsoft Excel」で使えるVBAを紹介する。※ショートカットキー、アクセスキーの解説あり
![]() |
|
|
|
![]() |