在多線程或者多進程編程中,有一個非常需要關注的東西,那就是同步以及互斥問題。
同步是指多個進程之間的協作,而互斥是指多個進程之間,為了爭奪有限的資源,而進行的競爭。
理論很高端,但經過自己幾天的學習,發現操作系統中,線程的信號量還是比較簡單易懂的……
————————————————————————————————————————
信號量是用來解決線程間同步或互斥的一種機制,也是一個特殊的變量,變量的值代表着當前可以利用的資源。
如果等於0,那就意味着現在沒有資源可用。
根據信號量的值可以將信號量分為二值信號量和計數信號量:
(計數信號量)就像一間公共廁所,里面一共有十個坑(最大是32767),算是十個資源。在同一時間可以容納十個人,當滿員的時候,外面的人必須等待里面的人出來,釋放一個資源,然后才能在進一個,當他進去之后,廁所又滿員了,外面的人還得繼續等待……
(二值信號量)就像自己家的衛生間,一般只有一個馬桶,在同一時間只能有一個人來用。
信號量只能進程兩個原子操作,P操作和V操作,
概念:
原子操作,就是不能被更高等級中斷搶奪優先的操作。
由於操作系統大部分時間處於開中斷狀態,所以,一個程序在執行的時候可能被優先級更高的線程中斷。
而有些操作是不能被中斷的,不然會出現無法還原的后果,這時候,這些操作就需要原子操作。就是不能被中斷的操作。
P操作:如果有可用的資源(信號量>0),那么占用一個資源(信號量-1)。如果沒有可用的資源(信號量=0),則進程被阻塞,直到系統重新給他分配資源。
V操作:如果在該信號量的等待隊列中有進程在等待該資源,則喚醒一個進程,否則釋放一個資源(信號量+1)
POSIX提供兩種信號量,有名信號量和無名信號量,有名信號量一般是用在進程間同步,無名信號量一般用在線程間同步。
兩種信號量的操作流程,大概有下面的幾點不同:
主要在於兩種信號量初始化和銷毀的方式不同。
對了,還有一點是非常需要注意的,和在操作共享內存時需要連接庫一樣,在編譯信號量的時候,也需要加上-pthread參數。
————————————————————————————————————————————————————————————
首先先來學習有名信號量
創建有名信號量:
創建或者打開一個信號量,需要使用sem_open()函數,函數原形如下:
sem_t sem_open(const char * name, int oflag, mode_t mode, unsigned int value)
返回值sem_t 是一個結構,如果函數調用成功,則返回指向這個結構的指針,里面裝着當前信號量的資源數。
參數name,就是信號量的名字,兩個不同的進程通過同一個名字來進行信號量的傳遞。
參數oflag,當他是O_CREAT時,如果name給出的信號量不存在,那么創建,此時必須給出mode和vaule。當他是O_EXCL時,好像沒有啥太重要的意義。
參數mode,很好理解,用來指定信號量的權限。
參數vaule,則是信號量的初始值。
關閉有名信號量:
關閉有名信號量所使用的函數是sem_close(sem_t *sem)
這個函數只有一個參數,意義也非常明顯,就是指信號量的名字。
信號量操作:
前面已經說過,在使用信號量時,有兩個非常重要的操作
P操作:使用的函數是sem_wait(sem_t *sem)
如果信號量的值大於零,sem_wait函數將信號量減一,並且立即返回。如果信號量的值小於零,那么該進程會被阻塞在原地。
V操作:使用的函數是sem_post(sem_t *sem)
當一個進程使用完某個信號量時,他應該調用sem_post函數來告訴系統收回資源。
sem_post函數和sem_wait函數的功能剛好相反,他會把指定的信號量加一
刪除有名信號量:
當使用完有名信號后,需要調用函數sem_unlink來釋放資源。
函數原形:int sem_unlink(const char *name)
——————————————————————————————————————————————————————————
實戰演練!!!!
需求:創建兩個進程,A進程打印A,然后等待B進程打印B,在B進程打印完了后,A進程在打印C。
A進程代碼如下:
#include<stdio.h> #include<stdlib.h> #include<semaphore.h> #include<errno.h> #include<sys/stat.h> #include<fcntl.h> #define SEM_NAME "name" int main() { sem_t *sem_test; sem_test = sem_open("ni", O_CREAT, 0644, 0); if(sem_test < 0) { printf("A進程創建信號量失敗!errno=%d\n",errno); exit(-1); } printf("進程A進入等待……\n"); printf("A\n"); sem_wait(sem_test); printf("C\n"); sem_post(sem_test); printf("A進程執行完畢!\n");
sem_close(sem_test);
sem_unlink("ni");
return 0;
}
B進程代碼如下:
#include<stdio.h> #include<stdlib.h> #include<semaphore.h> #include<errno.h> #include<sys/stat.h> #include<fcntl.h> #define SEM_NAME "name" int main() { sem_t *sem_test; sem_test = sem_open("ni",0); if(sem_test < 0) { printf("B進程創建信號量失敗!errno=%d\n",errno); exit(-1); } printf("B\n"); sem_post(sem_test); printf("B進程執行完畢!\n"); sem_close(sem_test); sem_unlink("ni"); return 0; }
現在進程編譯(一定要記得在編譯選項后加上-pthread哦!!)
代碼執行結果!!
執行得很成功!!
值得一提的是,如果在執行中出現了段錯誤 (核心已轉儲)這種錯誤信息的話,最好是去/dev/shm/下看一下,看看是否有個黃色的文件,權限被設置的奇高!
我就遇到了這樣的問題。
哎!雖然整篇文章就這么短短的幾十行,但我可是足足奮斗了將近五個小時才搞懂!!
明天~繼續加油!!