Linuxで、C言語を用いてセマフォを活用する方法を、記述します。
セマフォとは、プロセスやスレッドの同時実行を制御するための、OSで用意された機構です。あらかじめ同時実行できる数を指定し、それをセマフォの値の初期値とします(初期値が1の場合は、ほぼミューテックスと等しい動作になります)。セマフォによって同時実行が制限される処理を実行する場合、プロセスはセマフォから実行権を貰います。これをP操作と言います。逆に実行権を返すことをV操作と言います、もし他のプロセスによって、全ての実行権が取られていた場合は、他のプロセスのV操作により、自プロセスがP操作が出来るようになるまで待ちます。
ちなみにLinuxには、SystemVとPOSIXのセマフォがあり、両者は別物ですので、注意して下さい。両者の差異は勉強中です。(プロセス単位、スレッド単位?)
以下の例は、acceptor内で任意の数の子プロセスを生成し、子プロセスがtransport内の処理を実行するものです。transport内の処理を逐次処理とするために、セマフォを使用しております。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #define PROCESS_MAX 5 // semctlのコマンド用(開発者がソース内で定義しなくては駄目) union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *_buf; }; void transport(int sem) { int res = 0; struct sembuf semb; // semop へ渡す設定内容の構造体 semb.sem_num = 0; semb.sem_op = -1; // セマフォから権限を取得する semb.sem_flg = SEM_UNDO; res = semop(sem, &semb, 1); printf("tansport started\n"); sleep(2); printf("tansport finished\n"); semb.sem_num = 0; semb.sem_op = 1; // セマフォから得た権限を返還する semb.sem_flg = SEM_UNDO; res = semop(sem, &semb, 1); } void acceptor() { int res = 0; int i = 0; int sem; union semun semcmd; // semctl へ渡すコマンド用の共用体 // セマフォを取得する sem = semget(IPC_PRIVATE, 1, 0666|IPC_CREAT); // セマフォに初期値を設定する(この値が、同時実行を許可する数になる) semcmd.val = 1; res = semctl(sem, 0, SETVAL, semcmd); while(i < PROCESS_MAX) { int pid; pid = fork(); // fork システムコールで、子プロセス生成 switch(pid) { case 0: // 子プロセスの場合に実行する transport(sem); exit(0); // 子プロセスはここで終了する break; } // 親プロセスは処理を続行する i++; } } int main() { acceptor(); }
本来は、acceptorの中で、前の子プロセスによるtransport処理が終了するまで、fork()システムコールの実行を待ち合わせるようにしたかったのですが、セマフォ操作を実行するプロセスが異なると、様々な制約が発生し、難しくなります。ある意味セマフォは一般的な同期処理を行なうために、安全な手法を提供してくれるのかもしれませんが、少し凝ったことを考えると、使いにくい印象を受けました。それこそ、共有メモリを使った方が、開発者の管理責任は増すものの、明らかに自由度は高くなります。
とにかく今回二日ほど試行してみて感じたことは、セマフォは特定処理の排他制御以外への利用は、なかなか難しいということです。