はじめに
排他制御を勉強したメモです。
Ubuntu Server 18.04.2 LTS
mutex
LOCK / UNLOCK の二値状態を持つ。( pthread_mutex )
引数は、pthread_mutex_t をとる。
ロックしているタスクのみ、ロックを解除できる。
バイナリセマフォに近いが、タスクが停止状態になった場合やロックされているタスクへの振る舞いが異なる。
どうやらデッドロックや優先度逆転現象を回避するプロトコルも用意されているらしい。
条件変数
mutex のハッピーセット。 ( pthread_cond )
実行開始を他のタスクに指示するトリガーとしても使う。
つくりたいもの
- メインスレッド1つに子スレッド2つ(ThreadA, ThreadB)の計3つ
- メインスレッドは、子スレッドが終了するまで wait
- ThreadB は、wait 状態
- ThreadA が変数 total に 2 加算したら ThreadB に切り替え
- ThreadB が変数 total に 3 加算したら ThreadA に切り替え
- 5往復したらスレッドを停止
- メインスレッドで total の値と「The End.」をコンソールに出力し終了
サンプルプログラム
なにはともあれ、サンプル。
プログラムが汚いのはご容赦ください。
#include <stdio.h>
#include <pthread.h>
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *ThreadA();
void *ThreadB();
//flag
int flagA = 1;
int flagB = 0;
int count; /* ループ回数 */
int total; /* 加算結果 */
int main(){
pthread_t threadA, threadB;
int retA, retB;
//mutex初期化
if(pthread_mutex_init( &mutex, NULL ) != 0){
perror("pthread_mutex_init failed");
}
/* スレッド作成 */
if((retA = pthread_create(&threadA, NULL, ThreadA, NULL)) != 0){
perror("pthread_creat : ThreadA failed");
}
if((retB = pthread_create(&threadB, NULL, ThreadB , NULL)) != 0){
perror("pthread_creat : ThreadB failed");
}
/* スレッド終了待ち */
if((retA = pthread_join(threadA, NULL)) != 0){
perror("pthread_join : threadA failed");
}
if((retB = pthread_join(threadB, NULL)) != 0){
perror("pthread_join : threadB failed");
}
puts("Main end.");
/* mutex 破棄 */
if(pthread_mutex_destroy(&mutex) != 0){
perror("pthread_mutex_destroy failed");
}
/* cond 破棄 */
if(pthread_cond_destroy(&cond) != 0){
perror("pthread_cond_destroy failed");
}
return 0;
}
void *ThreadA()
{
int r;
for(int loop = 0; loop < 5; loop++){
if((r = pthread_mutex_lock(&mutex)) != 0){
perror("pthread_mutex_lock : ThreadA failed");
}
while(flagA != 1){
/* ThreadA を wait */
if(pthread_cond_wait(&cond, &mutex) != 0){
perror("pthread_cond_wait failed");
}
}
/* 加算 */
total += 2;
count++;
printf("%2d : %2d\n", count, total);
flagA = 0;
flagB = 1;
/* ThreadB ロック解除 */
if(pthread_cond_broadcast(&cond) != 0){
perror("pthread_cond_broadcast failed");
}
if((r = pthread_mutex_unlock(&mutex)) != 0){
perror("pthread_mutex_unlock : ThreadA failed");
}
}
return NULL;
}
void *ThreadB()
{
int r;
for(int loop = 0; loop < 5; loop++){
if((r = pthread_mutex_lock(&mutex)) != 0){
perror("pthread_mutex_lock : ThreadB failed");
}
while(flagB != 1){
/* ThreadB を wait */
if(pthread_cond_wait(&cond, &mutex) != 0){
perror("pthread_cond_wait failed");
}
}
/* 加算 */
total += 3;
count++;
printf("%2d : %2d\n", count, total);
flagA = 1;
flagB = 0;
/* ThreadA ロック解除 */
if(pthread_cond_broadcast(&cond) != 0){
perror("pthread_cond_broadcast failed");
}
if((r = pthread_mutex_unlock(&mutex)) != 0){
perror("pthread_mutex_unlock : ThreadB failed");
}
}
return NULL;
}
出力結果
出力結果
1 : 2
2 : 5
3 : 7
4 : 10
5 : 12
6 : 15
7 : 17
8 : 20
9 : 22
10 : 25
Main end.
参考文献
- https://docs.oracle.com/cd/E19455-01/806-2732/6jbu8v6or/index.html
- http://hiroakiuno.hatenablog.com/entry/20070321/p1
変更履歴
@angel_p_57 様 ご指摘いただきありがとうございます。
以下の5点を変更しました。
- pthread_create() 時にキャストしていた 2019/07/31
→ スレッドを void ポインタ型に変更( C 言語の特性として、キャストでエラー処理するとなんでもかんでも通してしまうため、エラー箇所に漏れが生じる) - mutex 初期化の重複 2019/07/31
→ pthread_mutex_init() に統一 - pthread_cond_destroy() を追加2019/07/31
- pthread 関数の戻り値チェックをすべての関数に実装 2019/07/31
- condA と condB の2つが定義されていた 2019/07/31
→ cond を一つにした
→ pthread_cond_broadcast で全スレッドにシグナルを投げ、プログラムを実行するかをスレッド内で flag を用いて判定するようにした