進程間同步之--信號量
信號量分有名和無名信號量。它們的區別和管道及命名管道的區別類似。有名信號量要求創建一個文件,而無名信號量則直接保存在內存中。一,Posix信號量
Posex信號量接口總結(見下圖):
上麵一行是有名信號量,可於fifo相類比,其值保存在文件中,可用於進程和線程同步;
下麵一行是無名信號量,可與pipe相類比,其值保存在內存中,可用於進程和線程同步;
中間部分,是兩者的公用接口。
sem_open() sem_close(),sem_unlink() //有名信號量 \ |sem_wait(),sem_post() |/ / |sem_trywait(),sem_getvalue()|\sem_destroy() //無名信號量 sem_init()
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:
多線程使用信號量的簡單例子:
/* * simple_sem_app.c */ #include "all.h" /* 每個字符輸出的間隔時間 */ #define TEN_MILLION 5000000L #define BUFSIZE 1024 void *threadout(void *args); int main(int argc, char *argv[]) { int error; int i; int n; sem_t semlock; pthread_t *tids; if (argc != 2) { fprintf (stderr, "Usage: %s numthreads\n", argv[0]); return 1; } n = atoi(argv[1]); tids = (pthread_t *)calloc(n, sizeof(pthread_t)); if (tids == NULL) { perror("Failed to allocate memory for thread IDs"); return 1; } if (sem_init(&semlock, 0, 1) == -1) { perror("Failed to initialize semaphore"); return 1; } for (i = 0; i < n; i++) { if (error = pthread_create(tids + i, NULL, threadout, &semlock)) { fprintf(stderr, "Failed to create thread:%s\n", strerror(error)); return 1; } } for (i = 0; i < n; i++) { if (error = pthread_join(tids[i], NULL)) { fprintf(stderr, "Failed to join thread:%s\n", strerror(error)); return 1; } } return 0; } void *threadout(void *args) { char buffer[BUFSIZE]; char *c; sem_t *semlockp; struct timespec sleeptime; semlockp = (sem_t *)args; sleeptime.tv_sec = 0; sleeptime.tv_nsec = TEN_MILLION; snprintf(buffer, BUFSIZE, "This is thread from process %ld\n", (long)getpid()); c = buffer; /****************** entry section *******************************/ while (sem_wait(semlockp) == -1) if(errno != EINTR) { fprintf(stderr, "Thread failed to lock semaphore\n"); return NULL; } /****************** start of critical section *******************/ while (*c != '\0') { fputc(*c, stderr); c++; nanosleep(&sleeptime, NULL); } /****************** exit section ********************************/ if (sem_post(semlockp) == -1) fprintf(stderr, "Thread failed to unlock semaphore\n"); /****************** remainder section ***************************/ return NULL; }
說明:該例子來自於usp。
可以把sem_wait和sme_post調用去掉,看看效果,可以看到出現了交叉輸出的情況。
nanosleep調用隻是為了讓輸出的效果更明顯,沒有其他意義。
更多的例子見mypxsem/prodcons2-4.c
3. 有名信號量
有名信號量是把信號量的值保存在文件中,所以它可以用於線程也可以用於進程間的同步。
如下麵的形式:
sem_t *mutex; ... mutex = sem_open(pathname, O_CREAT | O_EXCL, FILE_MODE, 0); if ((childpid = fork()) == 0) { /* child */ ... sem_wait(mutext); ... } /* parent */ ... sem_post(mutex); ...
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 有名信號量的使用
例子:
/* * chainname.c */ #include "my_unpipc.h" #define BUFSIZE 1024 #define PERMS (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) #define FLAGS (O_CREAT | O_EXCL) static int getnamed(char *name, sem_t **sem, int val); int main (int argc, char *argv[]) { char buffer[BUFSIZE]; char *c; pid_t childpid = 0; int delay; volatile int dummy = 0; int i, n; sem_t *semlockp; if (argc != 4){ /* check for valid number of command-line arguments */ fprintf (stderr, "Usage: %s processes delay semaphorename\n", argv[0]); return 1; } n = atoi(argv[1]); delay = atoi(argv[2]); for (i = 1; i < n; i++) if ((childpid = fork()) > 0) /* father break */ break; snprintf(buffer, BUFSIZE, "i:%d process ID:%ld parent ID:%ld child ID:%ld\n", i, (long)getpid(), (long)getppid(), (long)childpid); c = buffer; if (getnamed(argv[3], &semlockp, 1) == -1) { perror("Failed to create named semaphore"); return 1; } while (sem_wait(semlockp) == -1) /* entry section */ if (errno != EINTR) { perror("Failed to lock semlock"); return 1; } while (*c != '\0') { /* critical section */ fputc(*c, stderr); c++; for (i = 0; i < delay; i++) dummy++; } if (sem_post(semlockp) == -1) { /* exit section */ perror("Failed to unlock semlock"); return 1; } if (wait(NULL) == -1) /* remainder section */ return 1; return 0; } static int getnamed(char *name, sem_t **sem, int val) { while (((*sem = sem_open(name, FLAGS , PERMS, val)) == SEM_FAILED) && (errno == EINTR)) ; if (*sem != SEM_FAILED) return 0; if (errno != EEXIST) return -1; while (((*sem = sem_open(name, 0)) == SEM_FAILED) && (errno == EINTR)) ; if (*sem != SEM_FAILED) return 0; return -1; }
以上代碼創建了一個進程鏈,若把sem_wait和sem_post調用去掉,可以看到輸出很混亂。
這是由於每個子進程都共享了父進程的文件表項,而且都指向打開的文件表項。
system v 信號量
===============
1, 該類信號量,與posix信號量不同。它表示的信號量集,而不是單個信號量。
可用於不同進程間的同步。
內核為每個信號量集,維護一個如下的信息結構:<sys/sem.h>
struct semid_ds { struct ipc_perm sem_perm; /* 信號量集的操作許可權限 */ struct sem *sem_base; /* 某個信號量sem結構數組的指針, 當前信號量集中的每個信號量對應其中一個數組元素 */ ushort sem_nsems; /* sem_base 數組的個數 */ time_t sem_otime; /* 最後一次成功修改信號量數組的時間 */ time_t sem_ctime; /* 成功創建時間 */ }; struct sem { ushort semval; /* 信號量的當前值 */ short sempid; /* 最後一次返回該信號量的進程ID號 */ ushort semncnt; /* 等待semval大於當前值的進程個數 */ ushort semzcnt; /* 等待semval變成0的進程個數 */ };
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 : 是操作結構的數組
struct sembuf { short sem_num; /* 信號量的數目: 0,1,...,nsems-1 */ short sem_op; /* 信號量操作 */ short sem_flg; /* 操作表示符 */ };
(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是信號在集合中的序號,
union semun { int val; /* cmd == SETVAL */ struct semid_ds *buf /* cmd == IPC_SET或者 cmd == IPC_STAT */ ushort *array; /* cmd == SETALL, 或 cmd = GETALL */ };
cmd是控製命令,參數可選
cmd取值如下:
GETVAL, SETVAL : semid集合中semnum信號量當前的semval值
GETALL,SETALL :semid集合中所有信號量的值。
IPC_RMID:刪除semid信號量集
GETPID:返回最後成功操作該信號的進程號。
IPC_STAT:返回semid集合中的struct semid_ds結構。
例子:
/* my_sem.c */ #include <sys/types.h> #include <sys/sem.h> #include <sys/ipc.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <stdio.h> int main (int argc, char **argv) { key_t ipckey; int semid; /*建立兩個信號燈結構*/ struct sembuf sem[2]; /* sembuf defined in sys/sem.h */ /* 創建IPC Key */ ipckey = ftok("/tmp/rich", 42); /* 創建信號量. 4 == READ, 2 == ALTER */ semid = semget(ipckey, 1, 0666 | IPC_CREAT); if (semid < 0) { printf("Error - %sn", strerror(errno)); _exit(1); } /*設置*/ /* These never change so leave them outside the loop */ sem[0].sem_num = 0; sem[1].sem_num = 0; sem[0].sem_flg = SEM_UNDO; /* Release semaphore on exit */ sem[1].sem_flg = SEM_UNDO; /* Release semaphore on exit */ while(1) { printf("[%s] Waiting for the semaphore to be releasedn\n", argv[1]); /* 設置兩個信號燈,燈1等待,燈2請求資源鎖 */ sem[0].sem_op = 0; /* Wait for zero */ sem[1].sem_op = 1; /* Add 1 to lock it*/ /*設置信號量集,兩個信號量*/ semop(semid, sem, 2); /*資源鎖區*/ printf("[%s] I have the semaphoren\n", argv[1]); sleep(rand() % 3); /* Critical section, sleep for 0-2 seconds */ sem[0].sem_op = -1; /* Decrement to unlock */ /*出鎖,對信號量1操作*/ semop(semid, sem, 1); printf("[%s] Released semaphoren\n", argv[1]); sleep(rand() % 3); /* Sleep 0-2 seconds */ } }
最後更新:2017-04-02 06:51:55