Aritalab:Lecture/Programming/C/Parallel
From Metabolomics.JP
< Aritalab:Lecture | Programming(Difference between revisions)
Line 1: | Line 1: | ||
==Posix スレッド== | ==Posix スレッド== | ||
− | Pthreads とは pthread.h ヘッダーファイルで定義される C 言語用のライブラリです。これを用いてメモリ共有型の並列プログラムを組むことができます。UNIX の gcc でコンパイルする時は gcc -pthread オプションをつけて下さい (cygwin環境ではつけなくてもコンパイル可能)。これから以下の | + | Pthreads とは pthread.h ヘッダーファイルで定義される C 言語用のライブラリです。これを用いてメモリ共有型の並列プログラムを組むことができます。UNIX の gcc でコンパイルする時は gcc -pthread オプションをつけて下さい (cygwin環境ではつけなくてもコンパイル可能)。これから以下の 2 項目について解説します。 |
# 排他制御 (mutual exclusion または mutex と呼ばれる処理。ロックの概念) | # 排他制御 (mutual exclusion または mutex と呼ばれる処理。ロックの概念) | ||
# 条件変数(ウェイトとシグナル) | # 条件変数(ウェイトとシグナル) | ||
− | |||
関連サイトとしては[https://computing.llnl.gov/tutorials/pthreads/#Pthread ローレンスリバモア国立研究所のチュートリアル]が秀逸です。並列プログラミング全般やOpenMPの解説もあります。まずは Java によるプログラミングの項目をみて、排他制御と条件変数の使い方を理解しておいて下さい。 | 関連サイトとしては[https://computing.llnl.gov/tutorials/pthreads/#Pthread ローレンスリバモア国立研究所のチュートリアル]が秀逸です。並列プログラミング全般やOpenMPの解説もあります。まずは Java によるプログラミングの項目をみて、排他制御と条件変数の使い方を理解しておいて下さい。 | ||
==スレッドの作成、排他制御== | ==スレッドの作成、排他制御== | ||
− | 具体例をみるのが一番なので、とにかく C コードを示すようにします。まず、i番目のスレッドがそれぞれ | + | 具体例をみるのが一番なので、とにかく C コードを示すようにします。まず、i番目のスレッドがそれぞれ 10i から 10*(i+1) までの数を足してそれらの総和を計算するプログラムをみてみます。 |
<pre> | <pre> | ||
#include<stdio.h> | #include<stdio.h> | ||
#include<pthread.h> | #include<pthread.h> | ||
− | #define NTHREAD | + | #define NTHREAD 5 |
/* スレッドに渡すデータはグローバル変数にする */ | /* スレッドに渡すデータはグローバル変数にする */ | ||
Line 25: | Line 24: | ||
int main(void); | int main(void); | ||
void* thread(int id, void* arg); | void* thread(int id, void* arg); | ||
+ | void* print(); | ||
void* thread(int id, void* arg) { | void* thread(int id, void* arg) { | ||
Line 32: | Line 32: | ||
sum += i; | sum += i; | ||
pthread_mutex_lock(&lock); | pthread_mutex_lock(&lock); | ||
− | + | global_sum[id] = sum; | |
− | + | global_sum[NTHREAD] += sum; | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
pthread_mutex_unlock(&lock); | pthread_mutex_unlock(&lock); | ||
− | pthread_exit( | + | pthread_exit(NULL); |
+ | } | ||
+ | |||
+ | void* print() { | ||
+ | /* グローバル変数の状態表示。ロック外に出しても良い */ | ||
+ | printf("summing ["); | ||
+ | for(j=0; j < NTHREAD; j++) | ||
+ | printf("%d ", global_sum[j]); | ||
+ | printf("] = "); | ||
+ | printf("%d\n", global_sum[NTHREAD]); | ||
} | } | ||
Line 55: | Line 58: | ||
for(i=0; i < NTHREAD; i++) | for(i=0; i < NTHREAD; i++) | ||
pthread_create(&callThd[i], &attr, thread, (void*)i); | pthread_create(&callThd[i], &attr, thread, (void*)i); | ||
− | |||
for(i=0; i < NTHREAD; i++) | for(i=0; i < NTHREAD; i++) | ||
pthread_join(callThd[i], &status); | pthread_join(callThd[i], &status); | ||
+ | pthread_attr_destroy(&attr); | ||
pthread_mutex_destroy(&lock); | pthread_mutex_destroy(&lock); | ||
pthread_exit(NULL); | pthread_exit(NULL); | ||
} | } | ||
</pre> | </pre> | ||
+ | 実行結果はつけませんが、流れを把握して下さい。プログラムの最後にある pthread_mutex_destroy や pthread_exit という関数は実行しなくても動くように見えますが、以下に書くように重要ですので注意しましょう。 | ||
; ロック変数 | ; ロック変数 | ||
− | : pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; <br/>のように定義します。最初に PTHREAD_MUTEX_INITIALIZER と値を入れても良いし、main 関数内から pthread_mutex_init(&lock, NULL); と呼んでも同じです。当たり前ですが、変数は最初 unlock されています。終了する時は pthread_mutex_destroy(&lock) | + | : pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; <br/>のように定義します。最初に PTHREAD_MUTEX_INITIALIZER と値を入れても良いし、main 関数内から pthread_mutex_init(&lock, NULL); と呼んでも同じです。当たり前ですが、変数は最初 unlock されています。終了する時は pthread_mutex_destroy(&lock) を忘れずに呼んで下さい。 |
; スレッドの状態 | ; スレッドの状態 | ||
− | : pthread_attr_setdetachstate 関数では、PTHREAD_CREATE_DETACHED または PTHREAD_CREATE_JOINABLE という属性を与えられます。前者はスレッドが終了するのを待たずに、main() | + | : pthread_attr_setdetachstate 関数では、PTHREAD_CREATE_DETACHED または PTHREAD_CREATE_JOINABLE という属性を与えられます。前者はスレッドが終了するのを待たずに、main()スレッドが終了してよいことを示します。後者はデフォルトの状態で、上のプログラムでは join して結果を待っています。(つまり上のプログラムでは pthread_attr_setdetachstate 関数を呼ばずに &attr の部分に全部 NULL を入れても動くはずです。)また、 main 関数で pthread_exit(NULL) するのを忘れてはいけません。そうしないと main 関数とともにプログラム自体も終了してしまいます。 |
; ロックの仕方 | ; ロックの仕方 | ||
− | : 各スレッドは pthread_mutex_lock(&lock) | + | : 各スレッドは pthread_mutex_lock(&lock) でロックをかけますが、もし既にロックされている場合はアンロックされるまでトライし続けます(デッドロックに注意)。ここで取得エラーを返させるには pthread_mutex_trylock(&lock) 関数を使います。 |
+ | |||
+ | ==条件変数== | ||
+ | では次に、数の集計を特定のスレッドにおこなわせるプログラムを作ってみましょう。条件変数として cval を用意し、集計するスレッドは pthread_cond_signal(&cval) を用いて他のスレッドが終了するのを待機します。再びプログラムを掲載しますが、以前のプログラムに対する変更は以下のとおりです。 | ||
+ | * 条件変数 cval の追加 | ||
+ | * watch スレッドの追加 | ||
+ | * main 関数における条件変数の設定と watch スレッドの実行 | ||
+ | <pre> | ||
+ | #include<stdio.h> | ||
+ | #include<pthread.h> | ||
+ | |||
+ | #define NTHREAD 5 | ||
+ | |||
+ | /* スレッドに渡すデータはグローバル変数にする */ | ||
+ | int global_sum[NTHREAD+1]; | ||
+ | /* スレッドの終了を待つための id 格納場所 */ | ||
+ | pthread_t callThd[NTHREAD+1]; | ||
+ | /* グローバル変数書き込み用ロック */ | ||
+ | pthread_mutex_t lock; | ||
+ | pthread_cond_t cval; | ||
+ | |||
+ | int main(void); | ||
+ | void* thread(void* arg); | ||
+ | void* watch(void* arg); | ||
+ | void* print(); | ||
+ | |||
+ | void* watch(void* arg) { | ||
+ | int i, done =0; | ||
+ | printf("start waiting\n"); | ||
+ | pthread_mutex_lock(&lock); | ||
+ | while (done == 0) { | ||
+ | pthread_cond_wait(&cval, &lock); | ||
+ | printf("woke up\n"); | ||
+ | done = 1; | ||
+ | for(i=0; i < NTHREAD; i++) | ||
+ | if (global_sum[i] == 0) done = 0; | ||
+ | } | ||
+ | pthread_mutex_unlock(&lock); | ||
+ | for(i=0; i < NTHREAD; i++) | ||
+ | global_sum[NTHREAD] += global_sum[i]; | ||
+ | print(); | ||
+ | pthread_exit(NULL); | ||
+ | } | ||
+ | |||
+ | void* thread(void* arg) { | ||
+ | int id, i, sum=0; | ||
+ | id = (int)arg; | ||
+ | for(i=10*id; i <= 10*(id+1); i++) | ||
+ | sum += i; | ||
+ | pthread_mutex_lock(&lock); | ||
+ | global_sum[id] = sum; | ||
+ | print(); | ||
+ | pthread_cond_signal(&cval); | ||
+ | pthread_mutex_unlock(&lock); | ||
+ | pthread_exit(NULL); | ||
+ | } | ||
+ | |||
+ | void* print() { | ||
+ | int j; | ||
+ | printf("summing ["); | ||
+ | for(j=0; j < NTHREAD; j++) | ||
+ | printf("%d ", global_sum[j]); | ||
+ | printf("] = "); | ||
+ | printf("%d\n", global_sum[NTHREAD]); | ||
+ | } | ||
+ | |||
+ | int main(void) { | ||
+ | long i; | ||
+ | void *status; | ||
+ | pthread_attr_t attr; | ||
+ | |||
+ | pthread_mutex_init(&lock, NULL); | ||
+ | pthread_cond_init(&cval, NULL); | ||
+ | pthread_attr_init(&attr); | ||
+ | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); | ||
+ | pthread_t tid; | ||
+ | pthread_create(&callThd[NTHREAD], &attr, watch, (void*)(NTHREAD)); | ||
+ | for(i=0; i < NTHREAD; i++) | ||
+ | pthread_create(&callThd[i], &attr, thread, (void*)i); | ||
+ | for(i=0; i < NTHREAD; i++) | ||
+ | pthread_join(callThd[i], &status); | ||
+ | pthread_attr_destroy(&attr); | ||
+ | pthread_mutex_destroy(&lock); | ||
+ | pthread_cond_destroy(&cval); | ||
+ | pthread_exit(NULL); | ||
+ | } | ||
+ | </pre> | ||
+ | 実行結果をみてみます。watch スレッドが何度か起きて結果が埋まっているか確認し、最後に集計していることがわかります。 | ||
+ | <pre> | ||
+ | $ ./a.out | ||
+ | start waiting | ||
+ | summing [55 0 0 0 0 ] = 0 | ||
+ | woke up | ||
+ | summing [55 165 0 0 0 ] = 0 | ||
+ | summing [55 165 275 0 0 ] = 0 | ||
+ | summing [55 165 275 385 0 ] = 0 | ||
+ | woke up | ||
+ | summing [55 165 275 385 495 ] = 0 | ||
+ | woke up | ||
+ | summing [55 165 275 385 495 ] = 1375 | ||
+ | </pre> |
Revision as of 11:25, 9 July 2012
Posix スレッド
Pthreads とは pthread.h ヘッダーファイルで定義される C 言語用のライブラリです。これを用いてメモリ共有型の並列プログラムを組むことができます。UNIX の gcc でコンパイルする時は gcc -pthread オプションをつけて下さい (cygwin環境ではつけなくてもコンパイル可能)。これから以下の 2 項目について解説します。
- 排他制御 (mutual exclusion または mutex と呼ばれる処理。ロックの概念)
- 条件変数(ウェイトとシグナル)
関連サイトとしてはローレンスリバモア国立研究所のチュートリアルが秀逸です。並列プログラミング全般やOpenMPの解説もあります。まずは Java によるプログラミングの項目をみて、排他制御と条件変数の使い方を理解しておいて下さい。
スレッドの作成、排他制御
具体例をみるのが一番なので、とにかく C コードを示すようにします。まず、i番目のスレッドがそれぞれ 10i から 10*(i+1) までの数を足してそれらの総和を計算するプログラムをみてみます。
#include<stdio.h> #include<pthread.h> #define NTHREAD 5 /* スレッドに渡すデータはグローバル変数にする */ int global_sum[NTHREAD+1]; /* スレッドの終了を待つための id 格納場所 */ pthread_t callThd[NTHREAD]; /* グローバル変数書き込み用ロック */ pthread_mutex_t lock; int main(void); void* thread(int id, void* arg); void* print(); void* thread(int id, void* arg) { int i, j, sum; sum=0; for(i=100*id; i <= 100*(id+1); i++) sum += i; pthread_mutex_lock(&lock); global_sum[id] = sum; global_sum[NTHREAD] += sum; pthread_mutex_unlock(&lock); pthread_exit(NULL); } void* print() { /* グローバル変数の状態表示。ロック外に出しても良い */ printf("summing ["); for(j=0; j < NTHREAD; j++) printf("%d ", global_sum[j]); printf("] = "); printf("%d\n", global_sum[NTHREAD]); } int main(void) { long i; void *status; pthread_attr_t attr; pthread_mutex_init(&lock, NULL); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_t tid; for(i=0; i < NTHREAD; i++) pthread_create(&callThd[i], &attr, thread, (void*)i); for(i=0; i < NTHREAD; i++) pthread_join(callThd[i], &status); pthread_attr_destroy(&attr); pthread_mutex_destroy(&lock); pthread_exit(NULL); }
実行結果はつけませんが、流れを把握して下さい。プログラムの最後にある pthread_mutex_destroy や pthread_exit という関数は実行しなくても動くように見えますが、以下に書くように重要ですので注意しましょう。
- ロック変数
- pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
のように定義します。最初に PTHREAD_MUTEX_INITIALIZER と値を入れても良いし、main 関数内から pthread_mutex_init(&lock, NULL); と呼んでも同じです。当たり前ですが、変数は最初 unlock されています。終了する時は pthread_mutex_destroy(&lock) を忘れずに呼んで下さい。
- スレッドの状態
- pthread_attr_setdetachstate 関数では、PTHREAD_CREATE_DETACHED または PTHREAD_CREATE_JOINABLE という属性を与えられます。前者はスレッドが終了するのを待たずに、main()スレッドが終了してよいことを示します。後者はデフォルトの状態で、上のプログラムでは join して結果を待っています。(つまり上のプログラムでは pthread_attr_setdetachstate 関数を呼ばずに &attr の部分に全部 NULL を入れても動くはずです。)また、 main 関数で pthread_exit(NULL) するのを忘れてはいけません。そうしないと main 関数とともにプログラム自体も終了してしまいます。
- ロックの仕方
- 各スレッドは pthread_mutex_lock(&lock) でロックをかけますが、もし既にロックされている場合はアンロックされるまでトライし続けます(デッドロックに注意)。ここで取得エラーを返させるには pthread_mutex_trylock(&lock) 関数を使います。
条件変数
では次に、数の集計を特定のスレッドにおこなわせるプログラムを作ってみましょう。条件変数として cval を用意し、集計するスレッドは pthread_cond_signal(&cval) を用いて他のスレッドが終了するのを待機します。再びプログラムを掲載しますが、以前のプログラムに対する変更は以下のとおりです。
- 条件変数 cval の追加
- watch スレッドの追加
- main 関数における条件変数の設定と watch スレッドの実行
#include<stdio.h> #include<pthread.h> #define NTHREAD 5 /* スレッドに渡すデータはグローバル変数にする */ int global_sum[NTHREAD+1]; /* スレッドの終了を待つための id 格納場所 */ pthread_t callThd[NTHREAD+1]; /* グローバル変数書き込み用ロック */ pthread_mutex_t lock; pthread_cond_t cval; int main(void); void* thread(void* arg); void* watch(void* arg); void* print(); void* watch(void* arg) { int i, done =0; printf("start waiting\n"); pthread_mutex_lock(&lock); while (done == 0) { pthread_cond_wait(&cval, &lock); printf("woke up\n"); done = 1; for(i=0; i < NTHREAD; i++) if (global_sum[i] == 0) done = 0; } pthread_mutex_unlock(&lock); for(i=0; i < NTHREAD; i++) global_sum[NTHREAD] += global_sum[i]; print(); pthread_exit(NULL); } void* thread(void* arg) { int id, i, sum=0; id = (int)arg; for(i=10*id; i <= 10*(id+1); i++) sum += i; pthread_mutex_lock(&lock); global_sum[id] = sum; print(); pthread_cond_signal(&cval); pthread_mutex_unlock(&lock); pthread_exit(NULL); } void* print() { int j; printf("summing ["); for(j=0; j < NTHREAD; j++) printf("%d ", global_sum[j]); printf("] = "); printf("%d\n", global_sum[NTHREAD]); } int main(void) { long i; void *status; pthread_attr_t attr; pthread_mutex_init(&lock, NULL); pthread_cond_init(&cval, NULL); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_t tid; pthread_create(&callThd[NTHREAD], &attr, watch, (void*)(NTHREAD)); for(i=0; i < NTHREAD; i++) pthread_create(&callThd[i], &attr, thread, (void*)i); for(i=0; i < NTHREAD; i++) pthread_join(callThd[i], &status); pthread_attr_destroy(&attr); pthread_mutex_destroy(&lock); pthread_cond_destroy(&cval); pthread_exit(NULL); }
実行結果をみてみます。watch スレッドが何度か起きて結果が埋まっているか確認し、最後に集計していることがわかります。
$ ./a.out start waiting summing [55 0 0 0 0 ] = 0 woke up summing [55 165 0 0 0 ] = 0 summing [55 165 275 0 0 ] = 0 summing [55 165 275 385 0 ] = 0 woke up summing [55 165 275 385 495 ] = 0 woke up summing [55 165 275 385 495 ] = 1375