「pthread mutexで排他ロックする方法」の版間の差分
行1: | 行1: | ||
− | マルチスレッドプログラミングでスレッド間で共有データにアクセスするときに、mutex(MUTual EXclusion, ミューテックス)を用いて、排他ロックを行うことがあります。プログラムに競合状態を引き起こすようなコードがあると、計算の整合性、データの整合性が失われます。競合状態を避ける目的で、クリティカルリージョンをロックで保護します。[[pthread]] では、pthread_mutex_tとpthread_mutex_lock, pthread_mutex_unlock を用いて、ロックをコントロールします。 | + | マルチスレッドプログラミングでスレッド間で共有データにアクセスするときに、mutex(MUTual EXclusion, ミューテックス)を用いて、排他ロックを行うことがあります。プログラムに競合状態を引き起こすようなコードがあると、計算の整合性、データの整合性が失われます。競合状態を避ける目的で、クリティカルリージョンをロックで保護します。[[pthread]] では、pthread_mutex_tとpthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock を用いて、ロックをコントロールします。 |
'''読み方''' | '''読み方''' | ||
行11: | 行11: | ||
== 概要 == | == 概要 == | ||
+ | ロックには、 | ||
+ | * pthread_mutex_lock | ||
+ | * pthread_mutex_trylock | ||
+ | の2種類の関数が提供されています。 | ||
+ | pthread_mutex_lockは、すでにロックされているときに、ロックが解除されて、ロックを取得できるまで待ちます。pthread_mutex_unlockは、すでにロックされているときは、すぐに関数が返ります。 | ||
+ | |||
+ | |||
mutex1.c は、単純に2つのスレッドで1つのカウンタをインクリメントするだけのプログラムです。 | mutex1.c は、単純に2つのスレッドで1つのカウンタをインクリメントするだけのプログラムです。 | ||
mutexのロックがある場合と、ない場合の違いをみてみましょう。 | mutexのロックがある場合と、ない場合の違いをみてみましょう。 | ||
行45: | 行52: | ||
== pthread_mutex_lock == | == pthread_mutex_lock == | ||
pthread_mutex_lock は、以下のように動作します。 | pthread_mutex_lock は、以下のように動作します。 | ||
− | ;誰もロックされてない: | + | ;誰もロックされてない: ロックして、返ります。 |
;誰かがロックしている: アンロックされるまで待つ | ;誰かがロックしている: アンロックされるまで待つ | ||
+ | == pthread_mutex_trylock == | ||
+ | pthread_mutex_lock は、以下のように動作します。 | ||
+ | ;誰もロックされてない: ロックして、返ります。 | ||
+ | ;誰かがロックしている: アンロックを待たずに返ります。戻り値は、EBUSY が設定されています。 | ||
+ | == mutexの初期化 == | ||
+ | mutexは、初期化と破棄が必要です。 | ||
+ | |||
+ | mutexの初期化には、2種類の書き方ができます。 | ||
+ | <syntaxhighlight lang="c"> | ||
+ | pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; | ||
+ | pthread_mutex_init(&mutex, NULL); | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | <syntaxhighlight lang="c"> | ||
+ | int | ||
+ | pthread_mutex_init(pthread_mutex_t *mutex, | ||
+ | const pthread_mutexattr_t *attr); | ||
+ | </syntaxhighlight> | ||
+ | pthread_mutex_init()は、第2引数でpthread_mutexattr_tを渡すことにより、初期化パラメータを渡すことができます。NULLを渡すとデフォルトの値で初期化されます。 | ||
+ | |||
+ | <syntaxhighlight lang="c"> | ||
+ | pthread_mutex_destroy(&mutex); | ||
+ | </syntaxhighlight> | ||
− | == | + | == pthread_mutex_lockによる例 == |
=== ソースコード mutex1.c === | === ソースコード mutex1.c === | ||
<syntaxhighlight lang="c"> | <syntaxhighlight lang="c"> | ||
行191: | 行221: | ||
0.01 real 0.01 user 0.00 sys | 0.01 real 0.01 user 0.00 sys | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | == pthread_mutex_lockによる例 == | ||
+ | ここでは、pthread_mutex_trylockを利用した例です。ロックできないときは、諦めてcontinueだけします。ループの分だけ、カウントされるわけではありません。ループして、ロックができたときだけ、インクリメントします。 | ||
+ | == mutex_try1.c の例 == | ||
+ | === ソースコード mutex_try1.c === | ||
+ | <syntaxhighlight lang="c"> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <err.h> | ||
+ | #include <pthread.h> | ||
+ | #include <unistd.h> | ||
+ | |||
+ | const size_t loop_max = 65535; | ||
+ | |||
+ | pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; | ||
+ | int counter = 0; | ||
+ | |||
+ | void f1(); | ||
+ | void f2(); | ||
+ | |||
+ | int | ||
+ | main(int argc, char *argv[]) | ||
+ | { | ||
+ | pthread_t thread1, thread2; | ||
+ | int ret1,ret2; | ||
+ | |||
+ | ret1 = pthread_create(&thread1,NULL,(void *)f1,NULL); | ||
+ | ret2 = pthread_create(&thread2,NULL,(void *)f2,NULL); | ||
+ | |||
+ | if (ret1 != 0) { | ||
+ | err(EXIT_FAILURE, "can not create thread 1"); | ||
+ | } | ||
+ | if (ret2 != 0) { | ||
+ | err(EXIT_FAILURE, "can not create thread 2"); | ||
+ | } | ||
+ | |||
+ | printf("execute pthread_join thread1\n"); | ||
+ | ret1 = pthread_join(thread1,NULL); | ||
+ | if (ret1 != 0) { | ||
+ | err(EXIT_FAILURE, "can not join thread 1"); | ||
+ | } | ||
+ | |||
+ | printf("execute pthread_join thread2\n"); | ||
+ | ret2 = pthread_join(thread2,NULL); | ||
+ | if (ret2 != 0) { | ||
+ | err(EXIT_FAILURE, "can not join thread 2"); | ||
+ | } | ||
+ | |||
+ | printf("done\n"); | ||
+ | printf("%d\n", counter); | ||
+ | |||
+ | pthread_mutex_destroy(&m); | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | void | ||
+ | f1() | ||
+ | { | ||
+ | size_t i; | ||
+ | size_t try_again = 0; | ||
+ | |||
+ | for(i=0; i<loop_max; i++){ | ||
+ | int r; | ||
+ | r = pthread_mutex_trylock(&m); | ||
+ | if (r != 0) { | ||
+ | if (EBUSY == r) { | ||
+ | try_again++; | ||
+ | continue; | ||
+ | } | ||
+ | err(EXIT_FAILURE, "can not lock"); | ||
+ | } | ||
+ | counter++; | ||
+ | if (pthread_mutex_unlock(&m) != 0) { | ||
+ | err(EXIT_FAILURE, "can not unlock"); | ||
+ | } | ||
+ | } | ||
+ | printf("%s: try %lu\n", __func__, try_again); | ||
+ | } | ||
+ | |||
+ | void | ||
+ | f2() | ||
+ | { | ||
+ | size_t i; | ||
+ | size_t try_again = 0; | ||
+ | |||
+ | for(i=0; i<loop_max; i++){ | ||
+ | int r; | ||
+ | r = pthread_mutex_trylock(&m); | ||
+ | if (r != 0) { | ||
+ | if (EBUSY == r) { | ||
+ | try_again++; | ||
+ | continue; | ||
+ | } | ||
+ | err(EXIT_FAILURE, "can not lock"); | ||
+ | } | ||
+ | counter++; | ||
+ | if (pthread_mutex_unlock(&m) != 0) { | ||
+ | err(EXIT_FAILURE, "can not unlock"); | ||
+ | } | ||
+ | } | ||
+ | printf("%s: try %lu\n", __func__, try_again); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | === コンパイル === | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | cc -lpthread mutex_try1.c -o mutex_try1 | ||
+ | </syntaxhighlight> | ||
+ | === 実行例 === | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | % ./mutex_try1 | ||
+ | execute pthread_join thread1 | ||
+ | f2: try 49082 | ||
+ | f1: try 15857 | ||
+ | execute pthread_join thread2 | ||
+ | done | ||
+ | 66131 | ||
+ | </syntaxhighlight> | ||
+ | |||
== まとめ == | == まとめ == | ||
* クリティカルリージョンは、mutexで保護しましょう。 | * クリティカルリージョンは、mutexで保護しましょう。 |
2014年5月10日 (土) 21:42時点における版
マルチスレッドプログラミングでスレッド間で共有データにアクセスするときに、mutex(MUTual EXclusion, ミューテックス)を用いて、排他ロックを行うことがあります。プログラムに競合状態を引き起こすようなコードがあると、計算の整合性、データの整合性が失われます。競合状態を避ける目的で、クリティカルリージョンをロックで保護します。pthread では、pthread_mutex_tとpthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock を用いて、ロックをコントロールします。
読み方
- mutex
- みゅーてっくす
- 競合状態
- きょうごうじょうたい
- MUTual EXclusion
- みゅーちゃる えくすくるーじょん
- クリティカルセクション
- くりてぃかるせくしょん
- critial section
- くりてぃかるせくしょん
- critial region
- くりてぃかるりーじょん
目次
概要
ロックには、
- pthread_mutex_lock
- pthread_mutex_trylock
の2種類の関数が提供されています。 pthread_mutex_lockは、すでにロックされているときに、ロックが解除されて、ロックを取得できるまで待ちます。pthread_mutex_unlockは、すでにロックされているときは、すぐに関数が返ります。
mutex1.c は、単純に2つのスレッドで1つのカウンタをインクリメントするだけのプログラムです。
mutexのロックがある場合と、ない場合の違いをみてみましょう。
counter = counter + 1
mutex1.c のプログラムのロックとアンロックがない場合、2つのスレッドが同時に counter の値を取り出し、それぞれが数を足して、counter に代入した場合、本来、2つのスレッドがそれぞれ1を足して、2になるところ、結果的に1となりうるプログラムです。
for(i=0; i<loop_max; i++){ // lock なし counter++; }
lockとunlockで囲まれた領域は、ロックによって保護されます。 ロックとアンロックの間にある counter++ は、プロセス内部の処理としてはアトミックに処理されます。
for(i=0; i<loop_max; i++){ if (pthread_mutex_lock(&m) != 0) { err(EXIT_FAILURE, "can not lock"); } counter++; if (pthread_mutex_unlock(&m) != 0) { err(EXIT_FAILURE, "can not unlock"); } }
スレッド1がpthread_mutex_lockでロックしている間は、ほかのスレッド2が同じmutexに対して pthread_mutex_lock をしようとすると、スレッド1がアンロックするまで、スレッド2が待たされることになります。
ここでは、1つのロックしか扱いませんが、複数のmutexを扱う場合には、ロックの順番、アンロックの順番に注意しないと、デッドロック状態に陥ります。
pthread_mutex_lock
pthread_mutex_lock は、以下のように動作します。
- 誰もロックされてない
- ロックして、返ります。
- 誰かがロックしている
- アンロックされるまで待つ
pthread_mutex_trylock
pthread_mutex_lock は、以下のように動作します。
- 誰もロックされてない
- ロックして、返ります。
- 誰かがロックしている
- アンロックを待たずに返ります。戻り値は、EBUSY が設定されています。
mutexの初期化
mutexは、初期化と破棄が必要です。
mutexの初期化には、2種類の書き方ができます。
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_init(&mutex, NULL);
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
pthread_mutex_init()は、第2引数でpthread_mutexattr_tを渡すことにより、初期化パラメータを渡すことができます。NULLを渡すとデフォルトの値で初期化されます。
pthread_mutex_destroy(&mutex);
pthread_mutex_lockによる例
ソースコード mutex1.c
#include <stdio.h> #include <stdlib.h> #include <err.h> #include <pthread.h> #include <unistd.h> const size_t loop_max = 65535; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; int counter = 0; void f1(); void f2(); int main(int argc, char *argv[]) { pthread_t thread1, thread2; int ret1,ret2; ret1 = pthread_create(&thread1,NULL,(void *)f1,NULL); ret2 = pthread_create(&thread2,NULL,(void *)f2,NULL); if (ret1 != 0) { err(EXIT_FAILURE, "can not create thread 1"); } if (ret2 != 0) { err(EXIT_FAILURE, "can not create thread 2"); } printf("execute pthread_join thread1\n"); ret1 = pthread_join(thread1,NULL); if (ret1 != 0) { err(EXIT_FAILURE, "can not join thread 1"); } printf("execute pthread_join thread2\n"); ret2 = pthread_join(thread2,NULL); if (ret2 != 0) { err(EXIT_FAILURE, "can not join thread 2"); } printf("done\n"); printf("%d\n", counter); pthread_mutex_destroy(&m); return 0; } void f1() { size_t i; for(i=0; i<loop_max; i++){ if (pthread_mutex_lock(&m) != 0) { err(EXIT_FAILURE, "can not lock"); } counter++; if (pthread_mutex_unlock(&m) != 0) { err(EXIT_FAILURE, "can not unlock"); } } } void f2() { size_t i; for(i=0; i<loop_max; i++){ if (pthread_mutex_lock(&m) != 0) { err(EXIT_FAILURE, "can not lock"); } counter++; if (pthread_mutex_unlock(&m) != 0) { err(EXIT_FAILURE, "can not unlock"); } } }
コンパイル
cc -lpthread mutex1.c -o mutex1
実行例
% ./mutex1 execute pthread_join thread1 execute pthread_join thread2 done 131070
mutexを使用しないとどうなるのか?
これは、上のコードからmutexのロックとアンロックがないときのプログラムです。 mutex1.c は、単純に2つのスレッドで1つのカウンタをインクリメントするだけのプログラムです。
計算は、65535 * 2 = 131070 となるはずですが、3回実行して、違う結果になっています。
$ /usr/bin/time ./nolock execute pthread_join thread1 execute pthread_join thread2 done 115474 0.00 real 0.00 user 0.00 sys $ /usr/bin/time ./nolock execute pthread_join thread1 execute pthread_join thread2 done 111517 0.00 real 0.00 user 0.00 sys $ /usr/bin/time ./nolock execute pthread_join thread1 execute pthread_join thread2 done 122026 0.00 real 0.00 user 0.00 sys
mutexを使用した場合、どうなるのか?
mutex でカウンタの処理をアトミックにした場合、3回とも正しい結果になっています。 プログラムが単純過ぎて、比較の意味はほとんどありませんが、ロックのある場合とない場合で、ほんの少しだけ、処理時間がロックありの場合、多くなっていることがわかります。
$ /usr/bin/time ./lock execute pthread_join thread1 execute pthread_join thread2 done 131070 0.01 real 0.00 user 0.00 sys $ /usr/bin/time ./lock execute pthread_join thread1 execute pthread_join thread2 done 131070 0.01 real 0.00 user 0.00 sys $ /usr/bin/time ./lock execute pthread_join thread1 execute pthread_join thread2 done 131070 0.01 real 0.01 user 0.00 sys
pthread_mutex_lockによる例
ここでは、pthread_mutex_trylockを利用した例です。ロックできないときは、諦めてcontinueだけします。ループの分だけ、カウントされるわけではありません。ループして、ロックができたときだけ、インクリメントします。
mutex_try1.c の例
ソースコード mutex_try1.c
#include <stdio.h> #include <stdlib.h> #include <err.h> #include <pthread.h> #include <unistd.h> const size_t loop_max = 65535; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; int counter = 0; void f1(); void f2(); int main(int argc, char *argv[]) { pthread_t thread1, thread2; int ret1,ret2; ret1 = pthread_create(&thread1,NULL,(void *)f1,NULL); ret2 = pthread_create(&thread2,NULL,(void *)f2,NULL); if (ret1 != 0) { err(EXIT_FAILURE, "can not create thread 1"); } if (ret2 != 0) { err(EXIT_FAILURE, "can not create thread 2"); } printf("execute pthread_join thread1\n"); ret1 = pthread_join(thread1,NULL); if (ret1 != 0) { err(EXIT_FAILURE, "can not join thread 1"); } printf("execute pthread_join thread2\n"); ret2 = pthread_join(thread2,NULL); if (ret2 != 0) { err(EXIT_FAILURE, "can not join thread 2"); } printf("done\n"); printf("%d\n", counter); pthread_mutex_destroy(&m); return 0; } void f1() { size_t i; size_t try_again = 0; for(i=0; i<loop_max; i++){ int r; r = pthread_mutex_trylock(&m); if (r != 0) { if (EBUSY == r) { try_again++; continue; } err(EXIT_FAILURE, "can not lock"); } counter++; if (pthread_mutex_unlock(&m) != 0) { err(EXIT_FAILURE, "can not unlock"); } } printf("%s: try %lu\n", __func__, try_again); } void f2() { size_t i; size_t try_again = 0; for(i=0; i<loop_max; i++){ int r; r = pthread_mutex_trylock(&m); if (r != 0) { if (EBUSY == r) { try_again++; continue; } err(EXIT_FAILURE, "can not lock"); } counter++; if (pthread_mutex_unlock(&m) != 0) { err(EXIT_FAILURE, "can not unlock"); } } printf("%s: try %lu\n", __func__, try_again); }
コンパイル
cc -lpthread mutex_try1.c -o mutex_try1
実行例
% ./mutex_try1 execute pthread_join thread1 f2: try 49082 f1: try 15857 execute pthread_join thread2 done 66131
まとめ
- クリティカルリージョンは、mutexで保護しましょう。
- mutex で保護する領域は、最小限にしましょう。
- 複数の mutex を使用する場合には、ロックとアンロックの順番に気をつけましょう。