Posixセマフォ
セマフォの種類
1.Posix名前付きセマフォ。 | PosixのIPC名で識別され、プロセス間あるいはスレッド間の同期に用いる。 |
Posixメモリベースセマフォ | 共有メモリに置かれ、プロセス間あるいはスレッド間の同期に用いることが出来る。 |
System V セマフォ | カーネル内で管理され、プロセウ間あるいはスレッド間の同期に用いることが出来る。 |
セマフォの3種類の操作
作成時には呼び出し側が初期値を指定する必要がある。
セマフォ値を検査し、その値がゼロより小さいかまたは等しい場合には待機(ブロック)する。待機中に0より大きな値に変化したら、それを減じる。
このwait操作は、P操作(Pは、オランダ語で試行を意味するproberenの頭文字)と呼ばれる。
セマフォの値を増加させる。このセマフォの値がゼロ以上になるのを待ってブロックしているプロセスが存在する場合は、そのプロセスが1つ起こされる。
この操作は、V操作(Vは、オランダ語で増加を意味するverhogenの頭文字)
セマフォは、相互排除変数mutexのように用いることが出来る。相互排除変数mutexでは、アンロックロックするのは、そのmutexをロックしたスレッドに限られるが、セマフォに対するポスト操作は、そのセマフォに対する待機操作を行ったすレッドでなくても良い。
sem_open, sem_close, sem_unlink
sem_openは、新しい名前付きセマフォを作成するか、既存の名前付きセマフォをオープンする。名前付きセマフォは、どのような状況でもスレッドまたはプロセスの同期に用いることが出来る。#include <semaphore.h> sem_t *sem_open(const char *name,nt oflag,.. /* mode_t mode,nsigned int value */); 戻り値:成功ならセマフォへのポインタ、エラーならSEM_FAILED
#include <semaphore.h> int sem_close(sem_t *sem); 戻り値:成功なら0、エラーなら−1
セマフォは、クローズされてもシステムから削除されない。Posix名前付きセマフォは少なくともカーネル持続性を持ち、それをオープンしているプロセスが存在していなくても値を保持することを意味する。
システムから名前付きセマフォを削除するには、sem_unlinkを用いる。
#include <semaphore.h> int sem_unlink(const char *name); 戻り値:成功なら0、エラーなら-1
セマフォは、現在何回オープンされているかを管理する参照カウントを持ち、この関数はファイルに対するunlinkと同様の働きをする。参照カウントが0以上であってもnameは削除できるが、最後のsem_closeが呼び出されて参照カウントがゼロになるまで実際のセマフォオブジェクトは破壊されない。
sem_wait関数とsem_trywait関数
sem_waitは、指定したセマフォの値を検査し、それがゼロ以上であれば、値を減じて即座に制御を返す。呼び出したときに値がゼロであれば呼び出したスレッドは値がゼロより大きくなるまでスリープし、戻るときには値を1つ減らす。#include <semaphore.h> int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); 戻り値:2関数とも成功なら0、エラーなら-1
sem_waitとsem_trywaitの違いは、sem_trywaitがセマフォの値がゼロであっても呼び出したスレッドをスリープさせず、EAGAINを返すところ。sem_waitはシグナルに割り込まれると中途終了して、EINTRを返す。
sem_post関数とsem_getvalue関数
スレッドによる使用が終わったセマフォに対しては、sem_postを呼び出す。この操作でセマフォ値は1増加し、値が正になっているプロセスが存在すれば、そのプロセスを起こす。#include <semaphore.h> int sem_post(sem_t *sem); int sem_getvalue(sem_t *sem,int *valp); 戻り値:2関数とも成功なら0、エラーなら−1
sem_getvalueは、valpが指す整数にセマフォの現在値を返す。セマフォがロックされている場合、返される値はゼロ、またはそのセマフォ上で待機しているスレッド数を負数にした値。
セマフォはポスト操作で増加し、待機操作で減少するような値を持つので、セマフォ値が正になるのを待っているスレッドが存在しなくても、そのセマフォに対するポスト操作を実行できる。これに対し、スレッドがpthread_cond_signalを呼び出したとき、pthread_cond_waitの呼び出しでブロックしているスレッドが存在しない場合、そのシグナルは失われる。
//セマフォによる同期処理 #include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) sem_t *sem; int counter = 0; void* thread_func(void *arg) { int i; for (i = 0; i < 1000; i++) { sem_wait(sem); printf("counter : %d\n", counter++); sem_post(sem); } return NULL; } int main() { int i; pthread_t th[100]; int n = sizeof(th)/sizeof(th[0]); sem = sem_open("/sem", O_CREAT, FILE_MODE, 1); for (i = 0; i < n; i++) { pthread_create(&th[i], NULL, thread_func, NULL); } for (i = 0; i < n; i++) { pthread_join(th[i], NULL); } puts("Exit"); fflush(stdout); sem_close(sem); sem_unlink("/sem"); return 0; }
コンパイル、実行
>gcc -lrt sem.c
>./a.out
スレッドの配列を500にして、ソースコードをコンパイルし、
Fedora5にて、実行させたところ、SEGVで落ちた。
恐らく、作成できるスレッド数の上限を超えたためではないかと思う。厳密には、304個スレッドを生成するとSEGVで落ちた。
sem_init関数とsem_destory関数
Posix名前付きセマフォは、ファイルシステム内のファイルを参照するname引数で識別される。Posixでは、メモリベースのセマフォも提供している。メモリベースセマフォでは、アプリケーションがセマフォ用のメモリ(sem_t型変数)を割り当て、これをシステムに初期化させることでセマフォとして動作させる。#include <semaphore.h> int sem_init(sem_t *sem, int shared, unsigned int value); int sem_destroy(sem_t *sem); 戻り値:成功なら0、エラーなら−1
メモリベースセマフォは、名前を必要としない場合に用いることができる。名前付きセマフォは、そのセマフォを互いに関係を持たない複数のプロセスから利用する場合に用いることが多い。
//メモリベースセマフォ #include <semaphore.h> #include <stdio.h> sem_t sem; int counter = 0; void* thread_func(void *arg) { int i; for (i = 0; i < 1000; i++) { sem_wait(&sem); printf("counter : %d\n", counter); counter++; counter--; counter++; sem_post(&sem); } return NULL; } int main() { pthread_t th[100]; int n = sizeof(th)/sizeof(th[0]); int i; sem_init(&sem, 0, 1); for (i = 0; i < n; i++) { pthread_create(&th[i], NULL, thread_func, NULL); } for (i = 0; i < n; i++) { pthread_join(th[i], NULL); } sem_destroy(&sem); return 0; }