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

提供: C言語入門
移動: 案内検索
行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>
  
== mutex1.c の例 ==
+
== 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 を使用する場合には、ロックとアンロックの順番に気をつけましょう。

関連項目