天天看點

程序間同步之--信号量

     信号量分有名和無名信号量。它們的差別和管道及命名管道的差別類似。有名信号量要求建立一個檔案,而無名信号量則直接儲存在記憶體中。

一,posix信号量

posex信号量接口總結(見下圖):

上面一行是有名信号量,可于fifo相類比,其值儲存在檔案中,可用于程序和線程同步;

下面一行是無名信号量,可與pipe相類比,其值儲存在記憶體中,可用于程序和線程同步;

中間部分,是兩者的公用接口。

1.公共接口

1.1 接口函數說明

#include <semaphore.h>

int sem_wait(sem_t *sem);

    測試所指定信号量的值,它的操作是原子的。

    若sem>0,那麼它減1并立即傳回。

    若sem==0,則睡眠直到sem>0,此時立即減1,然後傳回。   

int sem_trywait(sem_t *sem);

    其他的行為和sem_wait一樣,除了:

    若sem==0,不是睡眠,而是傳回一個錯誤eagain。       

int sem_post(sem_t *sem);

    把指定的信号量sem的值加1;

    呼醒正在等待該信号量的任意線程。

int sem_getvalue(sem_t *sem, int *sval);

    取回信号量sem的目前值,把該值儲存到sval中。

    若有1個或更多的線程或程序調用sem_wait阻塞在該信号量上,該函數傳回兩種值:

        1) 傳回0

        2) 傳回阻塞在該信号量上的程序或線程數目

    linux采用傳回的第一種政策。

注意:在這些函數中,隻有sem_post是信号安全的函數,它是可重入函數。

1.2 接口使用的一般流程

sem_init(&sem);

sem_wait(&sem);

critical area;

sem_post(&sem);

remainder area

2.無名信号量

    無名信号量是儲存在變量類型為sem_t的記憶體中。

int sem_init(sem_t *sem, int pshared, unsigned int value);

    1)pshared==0 用于同一多線程的同步;

    2)若pshared>0 用于多個程序間的同步,此時sem必須放在共享記憶體中。

int sem_destroy(sem_t *sem);

    隻能銷毀由sem_init初始化的信号量,否則後果不可預料也。

例1:

    多線程使用信号量的簡單例子:

說明:該例子來自于usp。

    可以把sem_wait和sme_post調用去掉,看看效果,可以看到出現了交叉輸出的情況。

    nanosleep調用隻是為了讓輸出的效果更明顯,沒有其他意義。

更多的例子見mypxsem/prodcons2-4.c

3. 有名信号量

有名信号量是把信号量的值儲存在檔案中,是以它可以用于線程也可以用于程序間的同步。

如下面的形式:

3.1 常用函數說明

sem_t *sem_open(const char *name, int oflag,

                mode_t mode, unsigned int value);

    傳回一個sem_t類型的指針。該指針随後可用作sem_close等的參數。

    該函數參數的詳細資訊,可以參考手冊。

int sem_close(sem_t *sem);

    關閉sem信号量,并釋放資源。   

int sem_unlink(const char *name);

    在所有程序關閉信号量後删除name的信号量

3.2 有名信号量的使用

例子:

以上代碼建立了一個程序鍊,若把sem_wait和sem_post調用去掉,可以看到輸出很混亂。

這是由于每個子程序都共享了父程序的檔案表項,而且都指向打開的檔案表項。

system v 信号量

===============

1, 該類信号量,與posix信号量不同。它表示的信号量集,而不是單個信号量。

可用于不同程序間的同步。

核心為每個信号量集,維護一個如下的資訊結構:<sys/sem.h>

2, 信号量操作函數

a. 建立和打開信号量

int semget(key_t key, int nsems, int oflag)

(1) nsems>0  : 建立一個信的信号量集,指定集合中信号量的數量,一旦建立就不能更改。

(2) nsems==0 : 通路一個已存在的集合

(3) 傳回的是一個稱為信号量辨別符的整數,semop和semctl函數将使用它。

(4) 建立成功後一下結構被設定:

    .sem_perm 的uid和gid成員被設定成的調用程序的有效使用者id和有效組id

    .oflag 參數中的讀寫權限位存入sem_perm.mode

    .sem_otime 被置為0,sem_ctime被設定為目前時間

    .sem_nsems 被置為nsems參數的值

    .而于該集合中的每個信号量不初始化,這些結構是在semctl,用參數set_val,setall初始化的。

b. 設定信号量的值

int semop(int semid, struct sembuf *opsptr, size_t nops);

(1) semid 是semget傳回的semid

(2) nops : 是數組opsptr的個數

(3) opsptr : 是操作結構的數組

(4) 若sem_op 是正數,其值就加到semval上;

    若sem_op 是0,那麼調用者希望等到semval變為0,如果semval是0就反回;

    若sem_op 是負數,那麼調用者希望等待semval變為大于或等于sem_op的絕對值.

(5) sem_flg

    sem_undo     由程序自動釋放信号量

    ipc_nowait  不阻塞

c. 對信号量集實行控制操作

int semctl(int semid, int semnum, int cmd, ../* union semun arg */);

其中semid是信号量集合,semnum是信号在集合中的序号,

cmd是控制指令,參數可選

cmd取值如下:

getval, setval : semid集合中semnum信号量目前的semval值

getall,setall :semid集合中所有信号量的值。

ipc_rmid:删除semid信号量集

getpid:傳回最後成功操作該信号的程序号。

ipc_stat:傳回semid集合中的struct semid_ds結構。

例子: