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

提供: C言語入門
移動: 案内検索
(ページの作成:「マルチスレッドプログラミングでスレッド間で共有データにアクセスするときに、mutex(MUTual EXclusion, ミューテックス)を用いて...」)
(相違点なし)

2014年5月10日 (土) 16:14時点における版

マルチスレッドプログラミングでスレッド間で共有データにアクセスするときに、mutex(MUTual EXclusion, ミューテックス)を用いて、排他ロックを行うことがあります。プログラムに競合状態を引き起こすようなコードがあると、計算の整合性、データの整合性が失われます。競合状態を避ける目的で、クリティカルリージョンをロックで保護します。pthread では、pthread_mutex_tとpthread_mutex_lock, pthread_mutex_unlock を用いて、ロックをコントロールします。

読み方

mutex
みゅーてっくす
競合状態
きょうごうじょうたい
MUTual EXclusion
みゅーちゃる えくすくるーじょん
クリティカルセクション
くりてぃかるせくしょん
critial section
くりてぃかるせくしょん
critial region
くりてぃかるりーじょん

概要

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 は、以下のように動作します。

誰もロックされてない
ロックして、返る
誰かがロックしている
アンロックされるまで待つ

mutex1.c の例

ソースコード 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);
        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

まとめ

  • クリティカルリージョンは、mutexで保護しましょう。
  • mutex で保護する領域は、最小限にしましょう。
  • 複数の mutex を使用する場合には、ロックとアンロックの順番に気をつけましょう。

関連項目