進程通訊(三)--信號量

 信號量雖然也是進程通訊的一部分,但比起其他的通訊方式比如管道,消息隊列這些直接發送數據的形式不太相同。主要起到控制同步異步的作用。
  何爲同步異步?聽到同步不要因爲這個“同”字就聯想到倆進程同時運行。恰恰相反,同步往往要求一個進程等待另一個進程而達到協同作用。比如AB倆進程,A進程執行到了某段代碼需要   B進程運行返回的數據 ,那麼A就會等到了B進程執行直到B返回了A需要的數據時,A纔會繼續執行。這倆進程的關係是合作的,互相需要的,是同一條船上的,同一個步伐的,所以叫同步。
  異步就是AB進程自己執行自己的,他倆之間可能會存在交流或者通訊,但不是必需的,誰沒了誰也能照樣運行下去。
   


    我們之前說的管道,是一個進程A給裏面寫,另一個進程B從裏面讀。
   可以說數據長啥樣完全由A裏決定,B讀到啥就是啥。那有人肯定會說這不是廢話嗎。先別急着罵我,我們的信號量就不是這樣的。
  信號量相當於個什麼東西呢。相當個高級鎖,鎖的概念以前有說過。
這幾篇博客
   打個比方吧,就好像有兩個房子,房子中間有個管道連在一起。
   有名無名管道,消息隊列就好比一個房子的人給另一個房子的人傳了一封信,接受消息的進程 接受到的消息內容跟自己代碼中的一毛錢關係沒有,比如打印接受到的字符串,誰能想到打印的內容居然是某個進程發來“helloworld”?   
而信號量好比一個房子的人對着管道里喊,你可以看信了,而另一個房子裏的人收到喊聲後纔會打開自己房子裏本來就有的信封去看內容,即接受了特定批准後,執行自己代碼中早已準備好的部分,而那個部分平時不能亂執行,只有在需求到的時候才能執行,運行的代碼完全是自己實現準備好的。
自己早準備了一份helloworld字符串要打印,只是存在打印和不打印的情況,打印的內容肯定是helloworld,不可能是別的什麼東西。
醜陋的示意圖

常規通訊,例如管道等

信號量


那有人會說,那麼這不算通訊吧,既然信本來就在自己的房子裏何必又去等待指令後纔去看呢?就算看了也是自己本來都提前寫好的東西,沒有意義吧....

其實這的確算是通訊,舉個例子,B進程功能是輸出 “yes sir!”的,但必須接受到A的命令纔會打印。
A只有在觸發某些條件纔會給B發送命令,比如檢測到輸入爲“order B”,纔會讓信號量的值改變。B纔會因爲信號量值的增加而 執行那個yes sir。
這個過程下來不就是個簡單的通訊過程嗎?雖然B沒有接受到任何消息去打印,它只會打印自己代碼中早已經寫好的yes sir,但B進程能因爲A進程的檢測做出反應,這不就是通訊嗎?難道還是特異功能隔空感應啊?
其實信號量往往跟共享內存結合一起,共享內存就是多個進程同用一個內存,那麼信號量的左右不是凸顯出來了嗎。需要的時候更改自己的數據,防止臨界資源數據競爭,而共享內存就相當於通訊的進程們處理自己的東西。但是處理自己的東西時要有條有理,不可互相搶佔覆蓋,信號量剛好起到了制約協同的功能。

 扯了這麼多,還是來看看信號量的具體實現吧。
  信號量的原理就是個計數器。能被通訊之間進程共同看到的計數器,只有要計數器不爲0的時候,獲取信號量才能成功,獲取成功後就會讓計數器減少一個,這稱之爲p操作。
獲取信號量執行完自己該做的事情後,就要把信號量返還回去,計數器就會加1,這稱之爲v操作。

比如信號量初始只有一個,一個進程p操作了,然後信號量就變爲0了,其他進程想p就不行了,只能等待到信號量不爲0時纔可以,信號量不爲0只能在第一個進程執行完它的任務後使用v操作才能變爲1,這時其他進程纔可能使用p操作繼續運行。而其他沒有搶到那次p操作的,只能等剛搶到p的第二個進程運行一部分使用v歸還計數器了纔可能搶到p去停止等待。。。。。

剛只是限制了同時1個進程獲得信號量繼續運行。如果想要設置3個進程呢。信號量設置爲3就好了,所以說信號量很靈活,鎖就相當於值爲1的信號量。而信號量本身可以隨意設置數字,比起鎖來說就更強大了。

實現的函數如下

int semget((key_t)key,int nsems,int flag); //創建或獲取

第一個參數key就是自己隨便寫的數字,當作是自己代碼中的標記

第二個是信號量個數,它一般爲1,不要與信號量多少混淆,它代表一次創建幾個信號量,不要和信號量裏面具體設置的值分不清。簡單而言就是我們之前說的影響pv操作的是每個信號量的信號量多少,好比停車場裏是否有車位。車位不爲0就可以停車(p操作)。而這個函數的第二個參數好比是停車場的個數,一般都是一個停車場。

第三個參數就是好比創建文件函數中的那個 0664|0_CREAT一樣,不過這裏要換成IPC的IPC_CREAT

最後返回值就是標識符,好比文件函數中的fd,爲了後面一系列函數使用的一號參數


int  semop(int semid,struct sembuf  *buf,int lenth);  //改變信號量的值

這個函數就是用來改變信號量的值。p v操作都需要它,第二個參數就是個結構體指針,其中sem_op成員就是控制具體讓信號量加1還是減1,加1就設置爲1,減1就設爲-1

返回代表是否成功  -1表示失敗

int semctl(int semid,int pos,int cmd,);//信號量控制函數

解釋下第二個參數,常用的是IPC_RMID或IPC_SETVAL, 前者故名思意是刪除這個信號量,如果刪除可以忽略第三個參數。

後者是創建信號量後初始化用的,初始成爲什麼呢?初始成第三個參數聯合體裏的值。聯合體一般有三個成員,不過目前我們設置1個就好,設置個val成員。就是每個信號量的初始值,停車場裏初始車位是多少。如果爲1,1個車位,就代表可能進程們之間要搶這個車位了,競爭關係;如果爲0,就代表進程們直接要等待某個進程使用v操作開車走人了,才能執行p操作之後的內容,屬於一個進程爲其他進程服務,或者說是前後關係。

最後返回值也是表示是否成功  習以爲常的-1失敗


然而可以看出函數稍微有點複雜和亂,我們實際需要的功能需要封裝一下。

比如獲得信號量,我要先檢查信號量是否已經存在了,如果存在就直接用semget,如果不存在,我在semget創建後還要使用semctl進行初始化。

v  p操作也要先分別設置聯合體的爲1和-1,然後再使用semop函數改變信號量多少。

以下封裝函數代碼比起上課時多了些參數,在使用時也更靈活些。

頭文件sem.h



sem.c



測試個小例子,input程序負責輸入單詞,直到輸入ok了,B進程打印100內素數(幹啥都是,不打印素數或者打印helloworld也可以,要的是輸入ok後的給個反應)。

代碼:




結果:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章