非常非常非常.....的重要在共享內存的代碼里面p1.c實質是有問題lt._flag = 1;//這里先置1if(c == 'Q')sprintf(lt._buf,"quit");elsesprintf(lt._buf,"大家好,%d 我系渣渣輝. %d 是兄弟就來砍我吧!!! %d",i,i+1,i+2);while(*((int *)shmptr));//如果別人沒有把上一條消息給拿走,那我就卡在這里memcpy(shmptr,<,sizeof(lt));//copy到共享內存 我們是想copy之后別人拿這個信息我們是想copy之后別人拿這個信息 --- copy之后你根本就沒有告訴別人copy完是什么時候那么別人就可以在你copy中途的就過來拿p2 -> lt._flag等于1了就可以拿了lt._flag等于1是在copy的時間段里面前面那個時間就會將其置1p2看到變成1了,它就可以去讀后面內容了,p2很有可能拿到了一個錯誤的內容現在我要解決這個問題lt._flag = 0;//if(c == 'Q')sprintf(lt._buf,"quit");elsesprintf(lt._buf,"大家好,%d 我系渣渣輝. %d 是兄弟就來砍我吧!!! %d",i,i+1,i+2);while(*((int *)shmptr));//如果別人沒有把上一條消息給拿走,那我就卡在這里memcpy(shmptr,<,sizeof(lt));//copy到共享內存 我們是想copy之后別人拿這個信息*(int *)shmptr = 1;//再置1又有一個新的問題出現了,出現了p3 也操作這個共享內存,它也是往這個共享內存里面寫的p1正在寫的途中,p3過來一看 _flag == 0它也可以寫看誰搞得慢,誰慢就保留誰的信息上面不管有幾個進程在活動,不變的只有一個 --- 共享內存不變那么我可以對這個共享內存實現一個保護機制 --- 只要有進程在操作這個共享內存,別人都不能操作int flag;//這個flag在共享內存上面,有別與上面共享內存每一個進程在放在共享內存的時候都先過來看看flag的值如果flag == 0 ,我就不能訪問共享內存flag == 1,我就可以去訪問,訪問之前我先將flag調成0,然后訪問共享內存訪問完畢再調成1每一個進程在訪問之前都過來看這個flag,都遵守這個規則,那么就不會出現問題了p1 和 p2同時過來看flag的值,他們兩個同時發現flag == 1他們就去訪問這個共享內存了,這個保護機制又崩潰了要將保護機制繼續完善,誰過來訪問flag的時候其它的人都不能訪問如果你再弄一個保護的flag1,flag1又可能要成為保護的對象,這就成了一個死局了我們只需要解決不能同時過來操作flag的值 --- 原子操作可以解決(cpu產生的指令只會允許有一個進程過來操作)解決這個保護問題,并且實現原子操作的事情就是信號量解決的問題信號量就是一個以原子操作來實現的一個保鏢保證我們的共享資源有序訪問它是為了保護別人而生的,沒有保護對象就不需要信號量信號量是一種保護機制,對程序員的一個約定(程序員應該遵守,不是強制,程序員要知道如果你不遵守,你很有可能得到一個錯誤的值) 因此在并發里面信號量機制成為了必然信號量的操作流程首先實現 P操作(上鎖,上鎖的過程是原子操作)上鎖完畢,再過去訪問共享資源共享資源訪問完畢 -> 這個區域我們叫臨界區(約定:快進快出)如果你占著這個臨界區,結果你死了或者一直不進行V操作,這種情況我們叫死鎖(死鎖是一個非常嚴重的問題,我們不應該有死鎖的操作)再進行 V操作(解鎖)P操作:使其信號量遞減的操作V操作:使其信號量遞增的操作如我的flag = 1;P操作就是--flag;V操作就是++flagflag = 1 ->這種信號量我們叫互斥信號量 但是有的時候某一些共享資源支持多個進程同時訪問,那么這個時候flag > 1P操作一般我們認為是 --就可以了,但是有的時候可能一個進程需要多個資源,這個時候就不是--,它需要 -= nV操作有的時候就需要 += n信號量實現 --- 它是實現在內核上面的一個標志,對于它的操作保證了原子操作信號量有兩種System V semaphore -> 兩種標準 此標準一般用于進程System V信號量是一個信號量集,里面有多個信號量信號量集里面的信號量可以單獨操作的,實現對于不同共享資源或者同一個共享資源需要多個信號量的保護的需求POSIX semaphore -> 此標準一般用于線程struct semid_ds {struct ipc_perm sem_perm; /* 權限 */__kernel_time_t sem_otime; /* 最后 semop 時間 */__kernel_time_t sem_ctime; /* 最后 semctl() 時間 */struct sem *sem_base; /*信號量數組 */ = malloc(sizeof(struct sem) * nsems)struct sem_queue *sem_pending; /* 等在這個信號量隊列上面的進程 */struct sem_queue **sem_pending_last; /* 最后等的那個 */struct sem_undo *undo; /* 撤銷 */unsigned short sem_nsems; /* 信號量的個數,就是 sem_base有多少個元素*/ };struct sem//單個信號量的類型 {int value;//信號量的值pid_t sempid;//最后一次進行p/v操作的進程idint semncnt;//使其增長的進程數量int semzcnt;//使其變成0的進程數量 };創建一個信號量集semget - get a System V semaphore set identifierSYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);key:一個ipc key,如果你想操作同一個信號量,key必須一樣nsems:這個信號量集里面你想有幾個信號量,只有創建信號量集的時候有效打開的時候這個參數會被忽略這個數量一旦確定了,以后就不能改了semflg:標志IPC_CREAT | 權限 創建0打開一個信號量集返回值:成功返回信號集的id,失敗返回-1,同時errno被設置The values of the semaphores in a newly created set are indeterminate.新創建出來的信號量集里面的信號量的值是不確定的所以我們需要給這些信號量馬上設置值NAMEsemctl - System V semaphore control operations控制這個信號量集 SYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);semid:信號量集的idsemnum:信號量集里面有多個信號量,你現在要操作哪一個你是不是要給我指明從0開始 一直到 nsems - 1如果cmd表明的是操作所有的信號量,那么這個參數就被忽略了cmd:命令號IPC_STAT 復制IPC_SET 設置IPC_RMID 刪除GETALL 獲取所有GETVAL 獲取一個SETALL 設置所有SETVAL 設置一個.........(arg):根據第三個命令號來,命令不一樣,這個參數就會不一樣cmd == GETALL: 獲取所有的信號量的值因此第四個參數就需要弄一個什么樣子的東西進去,用于保存所有的信號量的值我們需要用一個unsigned short arr[nsems];//原先創建的時候nsems是多少,這里就給多少semctl(semid,0,GETALL,arr);cmd == GETVAL : 獲取單個信號量的值第二個參數為哪一個信號量第四個參數就被忽略了,獲取到的值通過返回值返回給你unsigned short value = semctl(semid,2,GETVAL);//獲取信號量集里面的第三個信號的值cmd == SETALL : 設置所有的信號量的值,第二個參數被忽略第四個參數就是unsigned short數組的首地址unsigned short arr[5] = {3,2,5,7,1};//你的信號量集里面有5個信號量semctl(semid,2,SETALL,arr);cmd == SETVAL : 設置某一個信號量的值第二個參數為哪一個信號量第四個參數就是一個int值int value = 4;semctl(semid,1,SETVAL,value);//設置信號量集里面的第二個信號量的值為4cmd == IPC_STAT/IPC_SET 獲取或者設置信號量集頭節點的信息第四個參數就是struct semid_ds的指針,第二個參數會被忽略struct semid_ds buf;semctl(semid,0,IPC_STAT,&buf);//將頭部信息弄到buf里面去buf里面改一些什么東西了,然后想將buf設置回去semctl(semid,0,IPC_SET,&buf);cmd == IPC_INFOstruct seminfo __buf;semctl(semid,0,IPC_INFO,&__buf);.......由于第四個參數比較多變,為了統一這個參數,可以按照如下共同體建立一個結構體什么時候用哪一個就行union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */};cmd == SETALL,你怎么用這個共同體給我解決union semun hehe;hehe.array = malloc(sizeof(unsigned short) * nsems);//nsems就是信號量的個數//將值賦值到這個數組里面去hehe.array[0] = 3;hehe.array[1] = 1;....hehe.array[nsems - 1] = n;semctl(semid,2,SETALL,hehe.array);SEMOP(2) Linux Programmer's Manual SEMOP(2)NAMEsemop, semtimedop - System V semaphore operationsSYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>struct sembuf arr[3];......int semop(int semid, struct sembuf *sops, size_t nsops);semid:你要操作哪一個信號集sops:操作的信息 有下面的結構struct sembuf{unsigned short sem_num; /* 你要操作信號量集里面的那一個信號量 */short sem_op; /* 信號量的操作 P/V */P就是讓信號量的值遞減V就是讓信號量的值遞增所以這里填負數就是 P -3 -> 將信號量的值 -= 3所以這里填正數就是 V +3 -> 將信號量的值 += 3short sem_flg; /* 操作標志 */0 -> 阻塞IPC_NOWAIT -> 非阻塞SEM_UNDO 撤銷這個標志很有意義,當有這個標志之后內核就會關注這個進程如果進程沒有解鎖就退出了,內核就會將這個進程操作的信號量的值恢復到P之前 -- 防止帶鎖退出而出現的死鎖問題};nsops:sops這個玩意兒是一個數組的首地址,因此我需要將數組的元素個數傳進去可能操作多個信號量int semtimedop(int semid, struct sembuf *sops, size_t nsops,const struct timespec *timeout);//這個函數多了一個timeout的操作//表示限時等待semop這個函數如果是阻塞上鎖,別人沒有釋放鎖的時候,它是上不上去的,這個時候就會卡在semopsemtimedop給了一個時間,這個時間到了就不等了struct timespec {__kernel_time_t tv_sec; /* 秒 */long tv_nsec; /* 納秒 */};//假設你要等 1s 500000ns的時間struct timespec tp;clock_gettime(CLOCK_REALTIME,&tp);//獲取現在的時間//往后等1s 500000ns的時間tp.tv_sec += 1;tp.tv_nsec += 500000;if(tp.tv_nsec > 1000000000)//進1了{tp.tv_sec++;tp.tv_nsec -= 1000000000;}semtimedop(,,,&tp);返回值:成功0,失敗-1,同時errno被設置死鎖問題:只能避免死鎖,如果出現死鎖你是你的代碼有問題,我們需要需要解決這個bug如果有一個變量i = 1;進程1對這個i++;進程2對這個i--兩個進程不知道什么時候會運行,請問,當兩個進程都運行完一遍之后這個i的值為多少進程1對i的操作可以分為三步:1讀取i的值放在自己的內存空間 2對內存進行++ 3將結果寫入到i的內存空間(這三步是原子操作)進程2對i的操作可以分為三步:4讀取i的值放在自己的內存空間 5對內存進行-- 6將結果寫入到i的內存空間(這三步是原子操作)145623 -> 2123456 -> 1456123 -> 1412356 -> 0451236 -> 0POSIX semaphore -> 單個信號量有名信號量:內容在內核里面,在文件系統里面有一個名字因此可以用于不同的進程間或者線程間無名信號量:沒有名字,因此只能通過遺傳,因此只能用于有親緣關系的進程間如果這個無名信號量存在于父進程的內存空間,當子進程拷貝過去之后,父子里面的信號量就變成兩個了這個時候父進程就會操作父進程,子進程操作的就是子進程的,這樣就起不到保護的作用了那么我們就需要讓這個信號存在于共享內存里面 --- 這種操作明顯很麻煩,不建議使用或者線程間有名信號量:需要創建或者打開 NAMEsem_open - initialize and open a named semaphoreSYNOPSIS#include <fcntl.h> /* For O_* constants */#include <sys/stat.h> /* For mode constants */#include <semaphore.h>sem_t *sem_open(const char *name, int oflag);//純粹的打開一個信號量sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);//這個玩意可以打開或者創建一個信號量name:一個存在的路徑名 這個路徑名里面只能有一個 /,開頭也是它"/hehe.sem"oflag:標志0 打開O_CREAT 創建標記mode:你創建的時候需要這個權限 0664value:創建的時候才會有值的設置,如果是打開一個信號量是沒有值的設置的只有創建成功才會被設置進去返回值:成功返回一個sem_t指針,指向我們的信號量失敗返回SEM_FAILED,同時errno被設置Link with -pthread.//鏈接這個庫 pthread 因此編譯的時候需要在后面加上 -lpthread無名信號量只能初始化 NAMEsem_init - initialize an unnamed semaphoreSYNOPSIS#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);sem:保存我們的信號量pshared:共享方式0 :進程的內部線程共享1 : 不同進程間的共享 ---- 這種不建議使用value:值成功0 失敗-1Link with -pthread.P操作 NAMEsem_wait, sem_timedwait, sem_trywait - lock a semaphore 上鎖SYNOPSIS#include <semaphore.h>int sem_wait(sem_t *sem);//等待版本 P操作int sem_trywait(sem_t *sem);//嘗試版本 非阻塞 P操作int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);//限時等待版本 P操作sem:你要對哪個信號量進程P操作Link with -pthread.V操作 NAMEsem_post - unlock a semaphore 解鎖SYNOPSIS#include <semaphore.h>int sem_post(sem_t *sem);sem:你要對哪個信號量進程P操作Link with -pthread.POSIX信號量的其它操作NAMEsem_getvalue - get the value of a semaphore獲取信號量的值 SYNOPSIS#include <semaphore.h>int sem_getvalue(sem_t *sem, int *sval);sem:你要對哪個信號量進程進行操作sval:值寫入到這個內存空間Link with -pthread.NAMEsem_close - close a named semaphore//關閉不是刪除關閉有名信號量 SYNOPSIS#include <semaphore.h>int sem_close(sem_t *sem);sem:你要對哪個信號量進程進行操作Link with -pthread.NAMEsem_unlink - remove a named semaphore//刪除一個有名信號量SYNOPSIS#include <semaphore.h>int sem_unlink(const char *name);name:路徑名Link with -pthread.NAMEsem_destroy - destroy an unnamed semaphore銷毀一個無名信號量 SYNOPSIS#include <semaphore.h>int sem_destroy(sem_t *sem);sem:你要銷毀哪個無名信號量Link with -pthread.正式項目里面一旦發現是多個進程/線程對一個資源進行讀寫,那么我們就要保護