Linuxシステムコール、POSIX mutex による排他制御
UNIX / Linux システムコール・プログラミング
POSIX mutex による排他制御
Unix系のOSにはセマフォも存在していますが、セマフォとの違いとしては、
・ミューテックスでは制御権を持てるのは1つだけ。
セマフォは複数の使用者を設定することが可能です。
セマフォで使用者を1つだけとした場合は、ミューテックスと同様の動作となります。このように0/1だけの操作となるようなセマフォ/ミューテックスはバイナリセマフォとも呼ばれます。
・ミューテックスは所有者の概念を持っている
セマフォでは、ロックするプログラムとアンロックするプログラムが別でも構わない。
(デッドロックの元になるので危険な実装なのでオススメはしない)
しかし、ミューテックスでは所有者の概念があるためロックをかけた使用者しかアンロックできない。この部分がセマフォと異なる部分です。
これらの特徴から、ミューテックスはスレッド間の排他制御でよく利用されます。
ミューテックスを使ったロックは、ロック動作を行ったスレッドしかアンロックできません。
それでは、
早速コードを示します。
mutex.c
このコードは、3つの同じエントリ関数のスレッドを生成します。
それぞれのスレッドは、引数で渡された文字列を1文字づつ大文字←→小文字変換してコンソールに表示し、処理を行った文字列の数を起動元に返します。
この場合、コンソールが3つのスレッドによって競合関係となるため、3つのスレッドの表示が混じってしまいます。
そこで、ミューテックスによる排他制御を行って文字列の処理が終わるまでコンソールを排他的に利用するようにしています。
また、ミューテックスはロックしたスレッドが所有者となるため、ロックを行ったスレッドがアンロックするまで開放することができません。
コンパイルは下記のようになります。
$ gcc mutex.c -o mutex -lpthread
このコードは、マクロでミューテックスのロック/アンロックを無効にできます。
下記でコンパイルすると、コンソールの表示が混じってしまうことが確認できます。
$ gcc mutex.c -o mutex -lpthread -DNO_MUTEX
ミューテックスのロックは、必要最低限かつ複雑な分岐となるような部分で行わないようにするべきです。スレッドがアンロックせずに抜けてしまった場合など、他のプロセスやスレッドからアンロックすることができないからです。
排他制御すべき資源は何か?
排他制御すべき期間=クリティカルセッションの範囲はどこか?
余計な排他制御は、他の処理を止めてしまうため、
排他制御の必要性、また対象となる期間を最小限にするなど
設計面での考慮が必要となります。
なお、このサンプルコードの排他制御の対象はコンソール(標準出力)で、1つの文字列の処理を終わるまでが排他禁止の期間としています。
POSIX mutex による排他制御
mutex(ミューテックス)とは?
mutex(ミューテックス)は、Mutual Exclusion の略で訳せば相互排他となります。Unix系のOSにはセマフォも存在していますが、セマフォとの違いとしては、
・ミューテックスでは制御権を持てるのは1つだけ。
セマフォは複数の使用者を設定することが可能です。
セマフォで使用者を1つだけとした場合は、ミューテックスと同様の動作となります。このように0/1だけの操作となるようなセマフォ/ミューテックスはバイナリセマフォとも呼ばれます。
・ミューテックスは所有者の概念を持っている
セマフォでは、ロックするプログラムとアンロックするプログラムが別でも構わない。
(デッドロックの元になるので危険な実装なのでオススメはしない)
しかし、ミューテックスでは所有者の概念があるためロックをかけた使用者しかアンロックできない。この部分がセマフォと異なる部分です。
これらの特徴から、ミューテックスはスレッド間の排他制御でよく利用されます。
ミューテックスを使ったロックは、ロック動作を行ったスレッドしかアンロックできません。
それでは、
早速コードを示します。
mutex.c
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <ctype.h> #include <unistd.h> pthread_mutex_t mutex; void* thread(void* arg); // スレッドエントリ // // Main thread. // int main(int argc, char **argv) { int ret; const char *arg1 = "Pneumonoultramicroscopicsilicovolcanoconiosis, Pneumonoultramicroscopicsilicovolcanoconiosis"; const char *arg2 = "Supercalifragilisticexpialidocious, Pseudopseudohypoparathyroidism, floccinaucinihilipilification"; const char *arg3 = "Lopadotemachoselachogaleokranioleipsanodrimhypotrimmatosilphioparaomelitokatakechymenokichlepikossyphophattoperisteralektryonoptekephalliokigklopeleiolagoiosiraiobaphetraganopterygon"; pthread_t thrd1, thrd2, thrd3; void *th_ret1, *th_ret2, *th_ret3; pthread_mutex_init(&mutex, NULL); // 3つのスレッドを生成 ret = pthread_create(&thrd1, NULL, thread, (void*)arg1); if (ret) { perror("pthread_create[Thread 1]"); exit(1); } ret = pthread_create(&thrd2, NULL, thread, (void*)arg2); if (ret) { perror("pthread_create[Thread 2]"); exit(1); } ret = pthread_create(&thrd3, NULL, thread, (void*)arg3); if (ret) { perror("pthread_create[Thread 3]"); exit(1); } // スレッドの完了を待ち合わせる pthread_join(thrd3, &th_ret1); pthread_join(thrd2, &th_ret2); pthread_join(thrd1, &th_ret3); #ifndef NO_MUTEX pthread_mutex_lock(&mutex); #endif printf("Thread 3 [%d]\n", *(int*)th_ret1); free(th_ret1); // 戻り値領域を開放 printf("Thread 2 [%d]\n", *(int*)th_ret2); free(th_ret2); // 戻り値領域を開放 printf("Thread 1 [%d]\n", *(int*)th_ret3); free(th_ret3); // 戻り値領域を開放 #ifndef NO_MUTEX pthread_mutex_unlock(&mutex); #endif pthread_mutex_destroy(&mutex); return 0; } // スレッドエントリ関数 // 文字を大文字小文字変換して表示、文字数を返す void* thread(void* arg) { int i; char* c = (char*)arg; int* ret = malloc(sizeof(int)); // 戻り値領域確保 #ifndef NO_MUTEX pthread_mutex_lock(&mutex); #endif for(i=0; i<strlen(c); i++) { if (isupper(*(c+i))) { printf("%c", (char)tolower(*(c+i))); } else { printf("%c", (char)toupper(*(c+i))); } usleep(1); } printf("\n"); #ifndef NO_MUTEX pthread_mutex_unlock(&mutex); #endif *ret = i; pthread_exit(ret); }
このコードは、3つの同じエントリ関数のスレッドを生成します。
それぞれのスレッドは、引数で渡された文字列を1文字づつ大文字←→小文字変換してコンソールに表示し、処理を行った文字列の数を起動元に返します。
この場合、コンソールが3つのスレッドによって競合関係となるため、3つのスレッドの表示が混じってしまいます。
そこで、ミューテックスによる排他制御を行って文字列の処理が終わるまでコンソールを排他的に利用するようにしています。
また、ミューテックスはロックしたスレッドが所有者となるため、ロックを行ったスレッドがアンロックするまで開放することができません。
コンパイルは下記のようになります。
$ gcc mutex.c -o mutex -lpthread
このコードは、マクロでミューテックスのロック/アンロックを無効にできます。
下記でコンパイルすると、コンソールの表示が混じってしまうことが確認できます。
$ gcc mutex.c -o mutex -lpthread -DNO_MUTEX
ミューテックスのロックは、必要最低限かつ複雑な分岐となるような部分で行わないようにするべきです。スレッドがアンロックせずに抜けてしまった場合など、他のプロセスやスレッドからアンロックすることができないからです。
排他制御すべき資源は何か?
排他制御すべき期間=クリティカルセッションの範囲はどこか?
余計な排他制御は、他の処理を止めてしまうため、
排他制御の必要性、また対象となる期間を最小限にするなど
設計面での考慮が必要となります。
なお、このサンプルコードの排他制御の対象はコンソール(標準出力)で、1つの文字列の処理を終わるまでが排他禁止の期間としています。
Have a Happy Hucking!!
Lightning Brains
Lightning Brains
コメント
コメントを投稿