「pthread mutexで排他ロックする方法」の版間の差分
(同じ利用者による、間の4版が非表示) | |||
行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_trylock''' は、すでにロックされているときは、すぐに関数が返ります。 | ||
+ | |||
mutex1.c は、単純に2つのスレッドで1つのカウンタをインクリメントするだけのプログラムです。 | mutex1.c は、単純に2つのスレッドで1つのカウンタをインクリメントするだけのプログラムです。 | ||
mutexのロックがある場合と、ない場合の違いをみてみましょう。 | mutexのロックがある場合と、ない場合の違いをみてみましょう。 | ||
行30: | 行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"); | ||
} | } | ||
} | } | ||
行45: | 行54: | ||
== pthread_mutex_lock == | == pthread_mutex_lock == | ||
pthread_mutex_lock は、以下のように動作します。 | pthread_mutex_lock は、以下のように動作します。 | ||
− | ;誰もロックされてない: | + | ;誰もロックされてない: ロックして、返ります。 |
;誰かがロックしている: アンロックされるまで待つ | ;誰かがロックしている: アンロックされるまで待つ | ||
+ | == pthread_mutex_trylock == | ||
+ | '''pthread_mutex_lock''' は、以下のように動作します。 | ||
+ | ;誰もロックされてない: ロックして、返ります。 | ||
+ | ;誰かがロックしている: アンロックを待たずに返ります。戻り値は、EBUSY が設定されています。 | ||
− | == mutex1.c | + | EBUSYは、errno.hで定義されているので、errno.hをincludeする必要があります。 |
+ | == 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> | ||
+ | |||
+ | == ロックありとなしについて == | ||
+ | コンパイル時に -DNOLOCK のオプションをつけることで、ロックなしでコンパイルされます。 | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | $ cc -DNOLOCK -lpthread mutex1.c | ||
+ | </syntaxhighlight> | ||
+ | == pthread_mutex_lockによる排他ロックの例 == | ||
=== ソースコード mutex1.c === | === ソースコード mutex1.c === | ||
<syntaxhighlight lang="c"> | <syntaxhighlight lang="c"> | ||
行56: | 行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; | ||
行75: | 行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) ); |
} | } | ||
行84: | 行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"); | |
} | } | ||
行90: | 行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 | ||
} | } | ||
} | } | ||
行190: | 行244: | ||
131070 | 131070 | ||
0.01 real 0.01 user 0.00 sys | 0.01 real 0.01 user 0.00 sys | ||
+ | </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 <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); | ||
+ | } | ||
+ | </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> | </syntaxhighlight> | ||
== まとめ == | == まとめ == | ||
行197: | 行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 を使用する場合には、ロックとアンロックの順番に気をつけましょう。