デッドロック:mutexの二重ロックによるデッドロック
(プログラムの概要)
排他制御に失敗するとスレッドが処理終了待ちになり動作不能に陥ってしまうことがあります。まずは前回の排他制御プログラムを使ってデッドロックを起こすとどうなるか見てみましょう。
|
1.二重ロック:pthread_mutex_lock()を2回行う
/*
* sample program
* Deadlock test program
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#define MAX_THREADS 10
#define MAX_CNT 10000
int grobal_cnt;
pthread_mutex_t mutex;
void *thread(void*);
static void countup(void);
int main(void)
{
int i = 0;
pthread_t thread_id[MAX_THREADS];
void *thread_return;
printf("sample program(%s) start\n", __FILE__);
pthread_mutex_init(&mutex, NULL);
/* create new threads */
for(i = 0; i < MAX_THREADS; i++){
if(pthread_create(&thread_id[i], NULL, thread, NULL) < 0){
perror("pthread_create error");
exit(1);
}
}
/* wait for the end of threads */
for(i = 0; i < MAX_THREADS; i++){
if(pthread_join(thread_id[i], &thread_return) < 0){
perror("pthread_join error");
exit(1);
}
}
printf("grobal_cnt = %d\n", grobal_cnt);
return 0;
}
void *thread(void *arg)
{
int i = 0;
printf("thread[%lu] start\n", pthread_self());
/* counter increment */
for(i = 0; i < MAX_CNT; i++){
pthread_mutex_lock(&mutex);
countup();
pthread_mutex_unlock(&mutex);
}
printf("thread[%lu] end\n", pthread_self());
pthread_exit(0);
}
static void countup(void)
{
int cnt = 0;
pthread_mutex_lock(&mutex);
cnt = grobal_cnt;
cnt++;
grobal_cnt = cnt;
pthread_mutex_unlock(&mutex);
return;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
(プログラムの概要)
10個のスレッドを生成し、各スレッドで外部メモリを1万回ずつカウントアップします。59~68L:二重にmutex_lock
前回からの変更点はカウントアップ部分をサブルーチンとして切り出し、サブルーチン内でもmutexのlock/unlockを行っています。
(コンパイルと動作)
gcc -Wall -o dead deadlock.c -lpthread
実行すると始めに生成されたthread()でpthread_mutex_lock()を行いmutexを獲得。その後サブルーチンcountup()をコールし、countup()内で再度pthread_mutex_lock()を行っています。2回目のlockはmutexの獲得待ちになりスレッドがスリープとなりますが、mutexを獲得しているのは自分自身である為永久に解放されずプログラム全体が停止してしまいます。
排他制御するべき処理がある程度大きくなった為サブルーチン化することは良くあると思います。そこでついでにサブルーチン内でmutex操作を行って閉じた処理にしたいと考えたとします。呼び元の全てでmutex操作を抜けば問題ないのですが、うっかり抜き忘れてしまったという場合にこのような二重ロックが発生してしまいます。また、複数の人で開発を行っていて、処理が複雑になるとどこでロックされているかプログラムを読み切らないで安易にロックをかけて二重ロックになってしまうこともあるかもしれません。もちろん二重ロックになる箇所を全てデバッグするのは当然ですが、実運用では安全を期して二重ロックを回避する下のような方法をお勧めします。
2.二重ロックを防ぐ
mutexの初期化により同じスレッドによる再帰的なmutex_lockを許すことができます。※カーネル2.4系では注意が必要。スレッドの実装方法がNPTL(Native POSIX Thread Library)に対応していないと動作しません。RedHatを例にすると9から実装されている為8では動作しません。
/*
* sample program
* Deadlock test program
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#define MAX_THREADS 10
#define MAX_CNT 10000
extern int pthread_mutexattr_settype(pthread_mutexattr_t*, int);
int grobal_cnt;
pthread_mutex_t mutex;
void *thread(void *arg);
static void countup(void);
int main(void)
{
int i = 0;
pthread_t thread_id[MAX_THREADS];
void *thread_return;
pthread_mutexattr_t attr;
printf("sample program(%s) start\n", __FILE__);
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&mutex, &attr);
/* create new threads */
for(i = 0; i < MAX_THREADS; i++){
if(pthread_create(&thread_id[i], NULL, thread, NULL) < 0){
perror("pthread_create error");
exit(1);
}
}
/* wait for the end of threads */
for(i = 0; i < MAX_THREADS; i++){
if(pthread_join(thread_id[i], &thread_return) < 0){
perror("pthread_join error");
exit(1);
}
}
printf("grobal_cnt = %d\n", grobal_cnt);
return 0;
}
void *thread(void *arg)
{
int i = 0;
printf("thread[%lu] start\n", pthread_self());
/* counter increment */
for(i = 0; i < MAX_CNT; i++){
pthread_mutex_lock(&mutex);
countup();
pthread_mutex_unlock(&mutex);
}
printf("thread[%lu] end\n", pthread_self());
pthread_exit(0);
}
static void countup(void)
{
int cnt = 0;
pthread_mutex_lock(&mutex);
cnt = grobal_cnt;
cnt++;
grobal_cnt = cnt;
pthread_mutex_unlock(&mutex);
return;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
(プログラムの概要)
mutex初期化用の属性であるpthread_mutexattr_t型のattrを使用してpthread_mutex_init()を実行します。28L:属性attrを初期化します。
29L:attrのタイプをPTHREAD_MUTEX_RECURSIVE_NPに設定し、再帰的なmutexロックを可能にします。
30L:第二引数にattrを使います。
(コンパイルと動作)
gcc -Wall -o recursive recursive.c -lpthread
実行すると二重ロックを行っていますが、grobal_cntは100000になり正常にプログラムが終了します。
(補足)
28~30Lのmutex初期化処理を16Lの宣言時にpthread_mutex_t mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
とすることもできます。(環境によりエラーとなる場合もあります。)14Lでpthread_mutexattr_settype()をexternしていますが、これは管理人の環境でwarningがでた為です。 次回はスレッド間のデータ転送、同期処理です。