「pthread mutexで排他ロックする方法」の版間の差分

提供: C言語入門
移動: 案内検索
 
(同じ利用者による、間の3版が非表示)
行12: 行12:
 
== 概要 ==
 
== 概要 ==
 
ロックには、
 
ロックには、
* pthread_mutex_lock
+
* '''pthread_mutex_lock'''
* pthread_mutex_trylock
+
* '''pthread_mutex_trylock'''
 
の2種類の関数が提供されています。
 
の2種類の関数が提供されています。
pthread_mutex_lockは、すでにロックされているときに、ロックが解除されて、ロックを取得できるまで待ちます。pthread_mutex_unlockは、すでにロックされているときは、すぐに関数が返ります。
+
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++){
if (pthread_mutex_lock(&m) != 0) {
+
int r;
err(EXIT_FAILURE, "can not lock");
+
r = pthread_mutex_lock(&m);
 +
if (r != 0) {
 +
errc(EXIT_FAILURE, r, "can not lock");
 
}
 
}
 
counter++;
 
counter++;
if (pthread_mutex_unlock(&m) != 0) {
+
r = pthread_mutex_unlock(&m);
err(EXIT_FAILURE, "can not unlock");
+
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''' は、初期化と破棄が必要です。
  
mutexの初期化には、2種類の書き方ができます。
+
'''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>
  
== pthread_mutex_lockによる例 ==
+
== ロックありとなしについて ==
 +
コンパイル時に -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) {
                 err(EXIT_FAILURE, "can not join thread 1");
+
                 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) {
                 err(EXIT_FAILURE, "can not join thread 2");
+
                 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++){
                 if (pthread_mutex_lock(&m) != 0) {
+
#ifndef NOLOCK
                         err(EXIT_FAILURE, "can not lock");
+
                 int r;
 +
                r = pthread_mutex_lock(&m);
 +
                if (r != 0) {
 +
                         errc(EXIT_FAILURE, r, "can not lock");
 
                 }
 
                 }
 +
#endif
 
                 counter++;
 
                 counter++;
                 if (pthread_mutex_unlock(&m) != 0) {
+
#ifndef NOLOCK
                         err(EXIT_FAILURE, "can not unlock");
+
                 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) {
                 err(EXIT_FAILURE, "can not join thread 1");
+
                 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) {
                 err(EXIT_FAILURE, "can not join thread 2");
+
                 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;
 
                         }
 
                         }
                         err(EXIT_FAILURE, "can not lock");
+
                         errc(EXIT_FAILURE, r, "can not lock");
 
                 }
 
                 }
 +
#endif
 
                 counter++;
 
                 counter++;
                 if (pthread_mutex_unlock(&m) != 0) {
+
#ifndef NOLOCK
                         err(EXIT_FAILURE, "can not unlock");
+
                 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 を使用する場合には、ロックとアンロックの順番に気をつけましょう。

関連項目