剛剛這篇文章學習了共享內存:http://www.cnblogs.com/charlesblc/p/6142139.html
里面也提到了共享內存,自己不進行同步,需要其他手段比如信號量來進行。那么現在就學習信號量咯。
信號燈(semaphore),也叫信號量。它是不同進程間或一個給定進程內部不同線程間同步的機制。信號燈包括posix有名信號燈、 posix基於內存的信號燈(無名信號燈)和System V信號燈(IPC對象)
方法一、利用POSIX有名信號燈實現共享內存的同步
有名信號量既可用於線程間的同步,又可用於進程間的同步。
兩個進程,對同一個共享內存讀寫,可利用有名信號量來進行同步。一個進程寫,另一個進程讀,利用兩個有名信號量semr, semw。semr信號量控制能否讀,初始化為0。 semw信號量控制能否寫,初始為1。
讀共享內存的程序示例代碼如下
semr = sem_open("mysem_r", O_CREAT | O_RDWR , 0666, 0); if (semr == SEM_FAILED) { printf("errno=%d\n", errno); return -1; } semw = sem_open("mysem_w", O_CREAT | O_RDWR, 0666, 1); if (semw == SEM_FAILED) { printf("errno=%d\n", errno); return -1; } if ((shmid = shmget(key, MAXSIZE, 0666 | IPC_CREAT)) == -1) { perror("semget"); exit(-1); } if ((shmadd = (char *)shmat(shmid, NULL, 0)) == (char *)(-1)) { perror("shmat"); exit(-1); } while (1) { sem_wait(semr); printf("%s\n", shmadd); sem_post(semw); }
寫共享內存的程序示例代碼如下
。。。。。。 //同讀的程序 while (1) { sem_wait(semw); printf(">"); fgets(shmadd, MAXSIZE, stdin); sem_post(semr); }
方法二、利用POSIX無名信號燈實現共享內存的同步
POSIX無名信號量是基於內存的信號量,可以用於線程間同步也可以用於進程間同步。若實現進程間同步,需要在共享內存中來創建無名信號量。
因此,共享內存需要定義以下的結構體。
typedef struct
{
sem_t semr;
sem_t semw;
char buf[MAXSIZE];
}SHM;
無名信號量的數據類型是:sem_t;
(1)初始化函數:
int sem_init(sem_t *sem, int pshared, unsigned value);
該函數將sem引用的無名信號量初始化為value,該參數表示擁有資源的個數,不能為負數。pshared指定為0,表示信號量只能由初始化這個信號量的進程使用,不能在進程間使用。一個無名信號量在被使用前必須先初始化。
該函數如果不成功將返回-1並設置errno。
(2)銷毀函數:
int sem_destroy(sem_t *sem);
該函數用來銷毀一個已經被初始化過的無名信號量。
如果不成功返回-1並設置errno。
(3)信號量操作函數:
intsem_wait(sem_t *sem);
該函數用來獲取資源,如果信號量為0,表示這時沒有相應資源空閑,那么調用線程就將掛起,直到有空閑資源可以獲取。如果信號量不為0,那么表示這時有相應資源可用,那么將信號量減1,並返回,表示獲取一個資源。
如果成功返回0,如果不成功,函數返回-1,並設置errno。值得注意的是,該函數是信號可中斷的,當正在等待資源的線程收到信號(可捕捉信號)時,該函數返回-1並把errno設置為EINTR。所以,必須在被信號中斷后重新啟動該函數,簡單代碼如下:
while((-1==sem_wait(&sem))&&(EINTR==errno));
sem_trywait(sem_t *sem);
該函數試圖獲取資源,當信號量為0時,它不阻塞,直接返回-1並將errno置為EAGAIN。
sem_post(sem_t *sem);
該函數實現了信號量的signal操作,如果沒有線程阻塞在該sem上,表示沒有線程等待該資源,這時該函數就對信號量的值進行增1操作,表示同類資源多增加了一個。如果至少有一個線程阻塞在該sem上,表示有線程等待資源,信號量為0,這時該函數保持信號量為0不變,並使某個阻塞在該sem上的線程從sem_wait函數中返回,表示有一個可用資源到達,並被某個線程占有,所以信號量還是為0。
讀、寫程序流程如下圖所示。
方法三、利用System V的信號燈實現共享內存的同步
System V的信號燈是一個或者多個信號燈的一個集合。其中的每一個都是單獨的計數信號燈。而Posix信號燈指的是單個計數信號燈
System V 信號燈由內核維護,主要函數semget,semop,semctl 。
一個進程寫,另一個進程讀,信號燈集中有兩個信號燈,下標0代表能否讀,初始化為0。 下標1代表能否寫,初始為1。
程序流程如下:
寫的流程和前邊的類似。
方法四、利用信號實現共享內存的同步
信號是在軟件層次上對中斷機制的一種模擬,是一種異步通信方式。利用信號也可以實現共享內存的同步。
思路:
reader和writer通過信號通信必須獲取對方的進程號,可利用共享內存保存雙方的進程號。
reader和writer運行的順序不確定,可約定先運行的進程創建共享內存並初始化。
利用pause, kill, signal等函數可以實現該程序(流程和前邊類似)。
還有這篇:
http://blog.csdn.net/ljianhui/article/details/10243617
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id是由semget返回的信號量標識符,sembuf結構的定義如下:
struct sembuf{ short sem_num;//除非使用一組信號量,否則它為0 short sem_op;//信號量在一次操作中需要改變的數據,通常是兩個數,一個是-1,即P(等待)操作, //一個是+1,即V(發送信號)操作。 short sem_flg;//通常為SEM_UNDO,使操作系統跟蹤信號量, //並在進程沒有釋放該信號量而終止時,操作系統釋放信號量 };
3、semctl函數
該函數用來直接控制信號量信息,它的原型為:
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四個參數,它通常是一個union semum結構,定義如下:
union semun{ int val; struct semid_ds *buf; unsigned short *arry; };
代碼如下:
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/sem.h> union semun { int val; struct semid_ds *buf; unsigned short *arry; }; static int sem_id = 0; static int set_semvalue(); static void del_semvalue(); static int semaphore_p(); static int semaphore_v(); int main(int argc, char *argv[]) { char message = 'X'; int i = 0; //創建信號量 sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT); if(argc > 1) { //程序第一次被調用,初始化信號量 if(!set_semvalue()) { fprintf(stderr, "Failed to initialize semaphore\n"); exit(EXIT_FAILURE); } //設置要輸出到屏幕中的信息,即其參數的第一個字符 message = argv[1][0]; sleep(2); } for(i = 0; i < 10; ++i) { //進入臨界區 if(!semaphore_p()) exit(EXIT_FAILURE); //向屏幕中輸出數據 printf("%c", message); //清理緩沖區,然后休眠隨機時間 fflush(stdout); sleep(rand() % 3); //離開臨界區前再一次向屏幕輸出數據 printf("%c", message); fflush(stdout); //離開臨界區,休眠隨機時間后繼續循環 if(!semaphore_v()) exit(EXIT_FAILURE); sleep(rand() % 2); } sleep(10); printf("\n%d - finished\n", getpid()); if(argc > 1) { //如果程序是第一次被調用,則在退出前刪除信號量 sleep(3); del_semvalue(); } exit(EXIT_SUCCESS); } static int set_semvalue() { //用於初始化信號量,在使用信號量前必須這樣做 union semun sem_union; sem_union.val = 1; if(semctl(sem_id, 0, SETVAL, sem_union) == -1) return 0; return 1; } static void del_semvalue() { //刪除信號量 union semun sem_union; if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1) fprintf(stderr, "Failed to delete semaphore\n"); } static int semaphore_p() { //對信號量做減1操作,即等待P(sv) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1;//P() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_p failed\n"); return 0; } return 1; } static int semaphore_v() { //這是一個釋放操作,它使信號量變為可用,即發送信號V(sv) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = 1;//V() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_v failed\n"); return 0; } return 1; }
上面程序,起兩個進程,X和O總是成對出現的,也就是說sleep的時候沒有被打斷。