Posixセマフォ

  • セマフォは、複数のプロセス間の同期や、あるプロセス中の複数のスレッド間の同期のために用いられる。
  • セマフォの種類

    1.Posix名前付きセマフォPosixのIPC名で識別され、プロセス間あるいはスレッド間の同期に用いる。
    Posixメモリベースセマフォ共有メモリに置かれ、プロセス間あるいはスレッド間の同期に用いることが出来る。
    System V セマフォカーネル内で管理され、プロセウ間あるいはスレッド間の同期に用いることが出来る。

    セマフォの3種類の操作

  • セマフォの作成(create)

  • 作成時には呼び出し側が初期値を指定する必要がある。

  • セマフォに対する待機(wait)

  • セマフォ値を検査し、その値がゼロより小さいかまたは等しい場合には待機(ブロック)する。待機中に0より大きな値に変化したら、それを減じる。
    このwait操作は、P操作(Pは、オランダ語で試行を意味するproberenの頭文字)と呼ばれる。

  • セマフォのポスト(post)

  • セマフォの値を増加させる。このセマフォの値がゼロ以上になるのを待ってブロックしているプロセスが存在する場合は、そのプロセスが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

  • namは、スラッシュから始まる文字列

  • oflagは、ゼロ、O_CREAT,_CREAT | O_EXCLのいずれか。O_CREATを指定する場合は第3引数と第4引数が必要。modeで許可ビット、valueセマフォの初期値を指定する。

  • #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

  • メモリベースのセマフォは、sem_initを呼び出して初期化する。

  • sharedがゼロならば、このセマフォはプロセス内のスレッド間で共有され、そうでなければプロセス間で共有される。

  • sharedが非ゼロの場合、セマフォはそれを使用するすべてのプロセスからアクセス可能な、何らかの共有メモリに格納されている必要がある。

  • value引数は、セマフォの初期値

  • 用済みのメモリベースセマフォは、sem_destroyで破壊する


  • sem_openは、shared引数やPTHREAD_PROCESS_SHARED属性を必要としない。名前付きセマフォが常に異なるプロセス間で共有可能だから

  • メモリベースセマフォには、O_CREATの概念がなく、sem_initは常にセマフォ値を初期化する。

  • sem_openは、sem_openが割り当てたsem_t型変数へのポインタを返す。

  • sem_initの第一引数は、呼び出し側が割り当てる必要があるsem_t型変数で、sem_initはこれを初期化する。


  • メモリベースセマフォは、名前を必要としない場合に用いることができる。名前付きセマフォは、そのセマフォを互いに関係を持たない複数のプロセスから利用する場合に用いることが多い。


  • メモリベースセマフォが単一プロセス内のスレッド間で共有されている場合(sem_initのshared=0 の場合)そのセマフォは、プロセス持続性を持ち、プロセスが終了するとセマフォも消滅する。

  • メモリベースセマフォが異なるプロセス間で共有されている場合(sem_initのshared=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;
    }