「pthread mutexで排他ロックする方法」の版間の差分
(同じ利用者による、間の3版が非表示) | |||
行12: | 行12: | ||
== 概要 == | == 概要 == | ||
ロックには、 | ロックには、 | ||
− | * pthread_mutex_lock | + | * '''pthread_mutex_lock''' |
− | * pthread_mutex_trylock | + | * '''pthread_mutex_trylock''' |
の2種類の関数が提供されています。 | の2種類の関数が提供されています。 | ||
− | + | pthread_mutex_lockは、すでにロックされているときに、ロックが解除されて、ロックを取得できるまで待ちます。 '''pthread_mutex_trylock''' は、すでにロックされているときは、すぐに関数が返ります。 | |
− | + | ||
mutex1.c は、単純に2つのスレッドで1つのカウンタをインクリメントするだけのプログラムです。 | mutex1.c は、単純に2つのスレッドで1つのカウンタをインクリメントするだけのプログラムです。 | ||
行37: | 行36: | ||
<syntaxhighlight lang="c"> | <syntaxhighlight lang="c"> | ||
for(i=0; i<loop_max; i++){ | for(i=0; i<loop_max; i++){ | ||
− | + | int r; | |
− | + | r = pthread_mutex_lock(&m); | |
+ | if (r != 0) { | ||
+ | errc(EXIT_FAILURE, r, "can not lock"); | ||
} | } | ||
counter++; | counter++; | ||
− | + | r = pthread_mutex_unlock(&m); | |
− | + | if (r != 0) { | |
+ | errc(EXIT_FAILURE, r, "can not unlock"); | ||
} | } | ||
} | } | ||
行55: | 行57: | ||
;誰かがロックしている: アンロックされるまで待つ | ;誰かがロックしている: アンロックされるまで待つ | ||
== pthread_mutex_trylock == | == pthread_mutex_trylock == | ||
− | pthread_mutex_lock は、以下のように動作します。 | + | '''pthread_mutex_lock''' は、以下のように動作します。 |
;誰もロックされてない: ロックして、返ります。 | ;誰もロックされてない: ロックして、返ります。 | ||
;誰かがロックしている: アンロックを待たずに返ります。戻り値は、EBUSY が設定されています。 | ;誰かがロックしている: アンロックを待たずに返ります。戻り値は、EBUSY が設定されています。 | ||
+ | |||
+ | EBUSYは、errno.hで定義されているので、errno.hをincludeする必要があります。 | ||
== mutexの初期化 == | == mutexの初期化 == | ||
− | + | '''mutex''' は、初期化と破棄が必要です。 | |
− | + | '''mutex''' の初期化には、2種類の書き方ができます。 | |
<syntaxhighlight lang="c"> | <syntaxhighlight lang="c"> | ||
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; | pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; | ||
行78: | 行82: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == | + | == ロックありとなしについて == |
+ | コンパイル時に -DNOLOCK のオプションをつけることで、ロックなしでコンパイルされます。 | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | $ cc -DNOLOCK -lpthread mutex1.c | ||
+ | </syntaxhighlight> | ||
+ | == pthread_mutex_lockによる排他ロックの例 == | ||
=== ソースコード mutex1.c === | === ソースコード mutex1.c === | ||
<syntaxhighlight lang="c"> | <syntaxhighlight lang="c"> | ||
行86: | 行95: | ||
#include <pthread.h> | #include <pthread.h> | ||
#include <unistd.h> | #include <unistd.h> | ||
+ | #include <string.h> | ||
const size_t loop_max = 65535; | const size_t loop_max = 65535; | ||
行105: | 行115: | ||
if (ret1 != 0) { | if (ret1 != 0) { | ||
− | err(EXIT_FAILURE, "can not create thread 1"); | + | err(EXIT_FAILURE, "can not create thread 1: %s", strerror(ret1) ); |
} | } | ||
if (ret2 != 0) { | if (ret2 != 0) { | ||
− | err(EXIT_FAILURE, "can not create thread 2"); | + | err(EXIT_FAILURE, "can not create thread 2: %s", strerror(ret2) ); |
} | } | ||
行114: | 行124: | ||
ret1 = pthread_join(thread1,NULL); | ret1 = pthread_join(thread1,NULL); | ||
if (ret1 != 0) { | if (ret1 != 0) { | ||
− | + | errc(EXIT_FAILURE, ret1, "can not join thread 1"); | |
} | } | ||
行120: | 行130: | ||
ret2 = pthread_join(thread2,NULL); | ret2 = pthread_join(thread2,NULL); | ||
if (ret2 != 0) { | if (ret2 != 0) { | ||
− | + | errc(EXIT_FAILURE, ret2, "can not join thread 2"); | |
} | } | ||
printf("done\n"); | printf("done\n"); | ||
printf("%d\n", counter); | printf("%d\n", counter); | ||
+ | |||
pthread_mutex_destroy(&m); | pthread_mutex_destroy(&m); | ||
return 0; | return 0; | ||
} | } | ||
− | + | void | |
− | void f1() | + | f1() |
{ | { | ||
size_t i; | size_t i; | ||
for(i=0; i<loop_max; i++){ | for(i=0; i<loop_max; i++){ | ||
− | + | #ifndef NOLOCK | |
− | + | int r; | |
+ | r = pthread_mutex_lock(&m); | ||
+ | if (r != 0) { | ||
+ | errc(EXIT_FAILURE, r, "can not lock"); | ||
} | } | ||
+ | #endif | ||
counter++; | counter++; | ||
− | + | #ifndef NOLOCK | |
− | + | r = pthread_mutex_unlock(&m); | |
+ | if (r != 0) { | ||
+ | errc(EXIT_FAILURE, r, "can not unlock"); | ||
} | } | ||
+ | #endif | ||
} | } | ||
} | } | ||
− | void f2() | + | |
+ | void | ||
+ | f2() | ||
{ | { | ||
size_t i; | size_t i; | ||
for(i=0; i<loop_max; i++){ | for(i=0; i<loop_max; i++){ | ||
+ | #ifndef NOLOCK | ||
if (pthread_mutex_lock(&m) != 0) { | if (pthread_mutex_lock(&m) != 0) { | ||
err(EXIT_FAILURE, "can not lock"); | err(EXIT_FAILURE, "can not lock"); | ||
} | } | ||
+ | #endif | ||
counter++; | counter++; | ||
+ | #ifndef NOLOCK | ||
if (pthread_mutex_unlock(&m) != 0) { | if (pthread_mutex_unlock(&m) != 0) { | ||
err(EXIT_FAILURE, "can not unlock"); | err(EXIT_FAILURE, "can not unlock"); | ||
} | } | ||
+ | #endif | ||
} | } | ||
} | } | ||
行229: | 行253: | ||
#include <stdlib.h> | #include <stdlib.h> | ||
#include <err.h> | #include <err.h> | ||
+ | #include <errno.h> | ||
#include <pthread.h> | #include <pthread.h> | ||
#include <unistd.h> | #include <unistd.h> | ||
+ | #include <string.h> | ||
const size_t loop_max = 65535; | const size_t loop_max = 65535; | ||
行250: | 行276: | ||
if (ret1 != 0) { | if (ret1 != 0) { | ||
− | err(EXIT_FAILURE, "can not create thread 1"); | + | err(EXIT_FAILURE, "can not create thread 1: %s", strerror(ret1) ); |
} | } | ||
if (ret2 != 0) { | if (ret2 != 0) { | ||
err(EXIT_FAILURE, "can not create thread 2"); | err(EXIT_FAILURE, "can not create thread 2"); | ||
+ | err(EXIT_FAILURE, "can not create thread 2: %s", strerror(ret2) ); | ||
} | } | ||
行259: | 行286: | ||
ret1 = pthread_join(thread1,NULL); | ret1 = pthread_join(thread1,NULL); | ||
if (ret1 != 0) { | if (ret1 != 0) { | ||
− | + | errc(EXIT_FAILURE, ret1, "can not join thread 1"); | |
} | } | ||
行265: | 行292: | ||
ret2 = pthread_join(thread2,NULL); | ret2 = pthread_join(thread2,NULL); | ||
if (ret2 != 0) { | if (ret2 != 0) { | ||
− | + | errc(EXIT_FAILURE, ret2, "can not join thread 2"); | |
} | } | ||
行282: | 行309: | ||
for(i=0; i<loop_max; i++){ | for(i=0; i<loop_max; i++){ | ||
+ | #ifndef NOLOCK | ||
int r; | int r; | ||
r = pthread_mutex_trylock(&m); | r = pthread_mutex_trylock(&m); | ||
行289: | 行317: | ||
continue; | continue; | ||
} | } | ||
− | + | errc(EXIT_FAILURE, r, "can not lock"); | |
} | } | ||
+ | #endif | ||
counter++; | counter++; | ||
− | + | #ifndef NOLOCK | |
− | + | r = pthread_mutex_unlock(&m); | |
+ | if (r != 0) { | ||
+ | errc(EXIT_FAILURE, r, "can not unlock"); | ||
} | } | ||
+ | #endif | ||
} | } | ||
printf("%s: try %lu\n", __func__, try_again); | printf("%s: try %lu\n", __func__, try_again); | ||
行306: | 行338: | ||
for(i=0; i<loop_max; i++){ | for(i=0; i<loop_max; i++){ | ||
+ | #ifndef NOLOCK | ||
int r; | int r; | ||
r = pthread_mutex_trylock(&m); | r = pthread_mutex_trylock(&m); | ||
行315: | 行348: | ||
err(EXIT_FAILURE, "can not lock"); | err(EXIT_FAILURE, "can not lock"); | ||
} | } | ||
+ | #endif | ||
counter++; | counter++; | ||
+ | #ifndef NOLOCK | ||
if (pthread_mutex_unlock(&m) != 0) { | if (pthread_mutex_unlock(&m) != 0) { | ||
err(EXIT_FAILURE, "can not unlock"); | err(EXIT_FAILURE, "can not unlock"); | ||
} | } | ||
+ | #endif | ||
} | } | ||
printf("%s: try %lu\n", __func__, try_again); | printf("%s: try %lu\n", __func__, try_again); | ||
行337: | 行373: | ||
66131 | 66131 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
== まとめ == | == まとめ == | ||
* クリティカルリージョンは、mutexで保護しましょう。 | * クリティカルリージョンは、mutexで保護しましょう。 | ||
行344: | 行379: | ||
== 関連項目 == | == 関連項目 == | ||
{{pthread}} | {{pthread}} | ||
+ | * [[スレッドセーフ]] | ||
<!-- vim: filetype=mediawiki | <!-- vim: filetype=mediawiki | ||
--> | --> |
2016年4月16日 (土) 10:55時点における最新版
マルチスレッドプログラミングでスレッド間で共有データにアクセスするときに、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_trylock は、すでにロックされているときは、すぐに関数が返ります。
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++){ int r; r = pthread_mutex_lock(&m); if (r != 0) { errc(EXIT_FAILURE, r, "can not lock"); } counter++; r = pthread_mutex_unlock(&m); if (r != 0) { errc(EXIT_FAILURE, r, "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 が設定されています。
EBUSYは、errno.hで定義されているので、errno.hをincludeする必要があります。
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);
ロックありとなしについて
コンパイル時に -DNOLOCK のオプションをつけることで、ロックなしでコンパイルされます。
$ cc -DNOLOCK -lpthread mutex1.c
pthread_mutex_lockによる排他ロックの例
ソースコード mutex1.c
#include <stdio.h> #include <stdlib.h> #include <err.h> #include <pthread.h> #include <unistd.h> #include <string.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: %s", strerror(ret1) ); } if (ret2 != 0) { err(EXIT_FAILURE, "can not create thread 2: %s", strerror(ret2) ); } printf("execute pthread_join thread1\n"); ret1 = pthread_join(thread1,NULL); if (ret1 != 0) { errc(EXIT_FAILURE, ret1, "can not join thread 1"); } printf("execute pthread_join thread2\n"); ret2 = pthread_join(thread2,NULL); if (ret2 != 0) { errc(EXIT_FAILURE, ret2, "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++){ #ifndef NOLOCK int r; r = pthread_mutex_lock(&m); if (r != 0) { errc(EXIT_FAILURE, r, "can not lock"); } #endif counter++; #ifndef NOLOCK r = pthread_mutex_unlock(&m); if (r != 0) { errc(EXIT_FAILURE, r, "can not unlock"); } #endif } } void f2() { size_t i; for(i=0; i<loop_max; i++){ #ifndef NOLOCK if (pthread_mutex_lock(&m) != 0) { err(EXIT_FAILURE, "can not lock"); } #endif counter++; #ifndef NOLOCK if (pthread_mutex_unlock(&m) != 0) { err(EXIT_FAILURE, "can not unlock"); } #endif } }
コンパイル
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 <errno.h> #include <pthread.h> #include <unistd.h> #include <string.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: %s", strerror(ret1) ); } if (ret2 != 0) { err(EXIT_FAILURE, "can not create thread 2"); err(EXIT_FAILURE, "can not create thread 2: %s", strerror(ret2) ); } printf("execute pthread_join thread1\n"); ret1 = pthread_join(thread1,NULL); if (ret1 != 0) { errc(EXIT_FAILURE, ret1, "can not join thread 1"); } printf("execute pthread_join thread2\n"); ret2 = pthread_join(thread2,NULL); if (ret2 != 0) { errc(EXIT_FAILURE, ret2, "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++){ #ifndef NOLOCK int r; r = pthread_mutex_trylock(&m); if (r != 0) { if (EBUSY == r) { try_again++; continue; } errc(EXIT_FAILURE, r, "can not lock"); } #endif counter++; #ifndef NOLOCK r = pthread_mutex_unlock(&m); if (r != 0) { errc(EXIT_FAILURE, r, "can not unlock"); } #endif } 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++){ #ifndef NOLOCK int r; r = pthread_mutex_trylock(&m); if (r != 0) { if (EBUSY == r) { try_again++; continue; } err(EXIT_FAILURE, "can not lock"); } #endif counter++; #ifndef NOLOCK if (pthread_mutex_unlock(&m) != 0) { err(EXIT_FAILURE, "can not unlock"); } #endif } 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 を使用する場合には、ロックとアンロックの順番に気をつけましょう。