linux多線程編程----信號量的使用
信號是E.W.Dijkstra在二十世紀六十年代末設(shè)計的一種編程架構(gòu)。Dijkstra的模型與鐵路操作有關(guān):假設(shè)某段鐵路是單線的,因此一次只允許一列火車通過。信號將用于同步通過該軌道的火車?;疖囋谶M入單一軌道之前必須等待信號燈變?yōu)樵试S通行的狀態(tài)。火車進入軌道后,會改變信號狀態(tài),防止其他火車進入該軌道?;疖囯x開這段軌道時,必須再次更改信號的狀態(tài),以便允許其他火車進入軌道。在計算機版本中,信號以簡單整數(shù)來表示。線程等待獲得許可以便繼續(xù)運行,然后發(fā)出信號,表示該線程已經(jīng)通過針對信號執(zhí)行P操作來繼續(xù)運行。線程必須等到信號的值為正,然后才能通過將信號值減1來更改該值。完成此操作后,線程會執(zhí)行V操作,即通過將信號值加1來更改該值。這些操作必須以原子方式執(zhí)行,不能再將其劃分成子操作,即,在這些子操作之間不能對信號執(zhí)行其他操作。在P操作中,信號值在減小之前必須為正,從而確保生成的信號值不為負,并且比該值減小之前小1。在P和V操作中,必須在沒有干擾的情況下進行運算。如果針對同一信號同時執(zhí)行兩個V操作,則實際結(jié)果是信號的新值比原來大2。對于大多數(shù)人來說,如同記住Dijkstra是荷蘭人一樣,記住P和V本身的含義并不重要。但是,真正學術(shù)的角度來說,P代表prolagen,這是由proberen te verlagen演變而來的杜撰詞,其意思是嘗試減小。V代表verhogen,其意思是增加。Dijkstra的技術(shù)說明EWD74中介紹了這些含義。sem_wait(3RT)和sem_post(3RT)分別與Dijkstra的P和V操作相對應。sem_trywait(3RT)是P操作的一種條件形式。如果調(diào)用線程不等待就不能減小信號的值,則該調(diào)用會立即返回一個非零值。有兩種基本信號:二進制信號和計數(shù)信號量。二進制信號的值只能是0或1,計數(shù)信號量可
以是任意非負值。二進制信號在邏輯上相當于一個互斥鎖。
不過,盡管不會強制,但互斥鎖應當僅由持有該鎖的線程來解除鎖定。因為不存在“持有信號的線程”這一概念,所以,任何線程都可以執(zhí)行V或sem_post(3RT)操作。計數(shù)信號量與互斥鎖一起使用時的功能幾乎與條件變量一樣強大。在許多情況下,使用計數(shù)信號量實現(xiàn)的代碼比使用條件變量實現(xiàn)的代碼更為簡單。但是,將互斥鎖用于條件變量時,會存在一個隱含的括號。該括號可以清楚表明程序受保護的部分。對于信號則不必如此,可以使用并發(fā)編程當中的go to對其進行調(diào)用。信號的功能強大,但是容易以非結(jié)構(gòu)化的不確定方式使用。
1 命名信號量和未命名信號量
POSIX信號可以是未命名的,也可以是命名的。未命名信號在進程內(nèi)存中分配,并會進行初始化。未命名信號可能可供多個進程使用,具體取決于信號的分配和初始化的方式。未命名信號可以是通過fork()繼承的專用信號,也可以通過用來分配和映射這些信號的常規(guī)文件的訪問保護功能對其進行保護。命名信號類似于進程共享的信號,區(qū)別在于命名信號是使用路徑名而非pshared值引用的。命名信號可以由多個進程共享。命名信號具有屬主用戶ID、組ID和保護模式。對于open、retrieve、close和remove命名信號,可以使用以下函數(shù):sem_open、sem_getvalue、sem_close和sem_unlink。通過使用sem_open,可以創(chuàng)建一個命名信號,其名稱是在文件系統(tǒng)的名稱空間中定義的。
2 計數(shù)信號量概述
從概念上來說,信號量是一個非負整數(shù)計數(shù)。信號量通常用來協(xié)調(diào)對資源的訪問,其中信號計數(shù)會初始化為可用資源的數(shù)目。然后,線程在資源增加時會增加計數(shù),在刪除資源時會減小計數(shù),這些操作都以原子方式執(zhí)行。如果信號計數(shù)變?yōu)榱?,則表明已無可用資源。計數(shù)為零時,嘗試減小信號的線程會被阻塞,直到計數(shù)大于零為止。
由于信號無需由同一個線程來獲取和釋放,因此信號可用于異步事件通知,如用于信號處理程序中。同時,由于信號包含狀態(tài),因此可以異步方式使用,而不用象條件變量那樣要求獲取互斥鎖。但是,信號的效率不如互斥鎖高。缺省情況下,如果有多個線程正在等待信號,則解除阻塞的順序是不確定的。信號在使用前必須先初始化,但是信號沒有屬性。
3 初始化信號量
使用sem_init(3RT)可以將sem所指示的未命名信號變量初始化為value。
sem_init語法
int sem_init(sem_t *sem, int pshared, unsigned int value);
#include <semaphore.h>
sem_t sem;
int pshared;
int ret;
int value;
/* initialize a private semaphore */
pshared =0;
value =1;
ret = sem_init(&sem, pshared, value);
如果pshared的值為零,則不能在進程之間共享信號。如果pshared的值不為零,則可以在進程之間共享信號。
注意:
(1)多個線程決不能初始化同一個信號。
(2)不得對其他線程正在使用的信號重新初始化。
#p#4 初始化進程內(nèi)信號量
pshared為0時,信號只能由該進程內(nèi)的所有線程使用。
#include <semaphore.h>
sem_t sem;
int ret;
int count = 4;
/* to be used within this process only */
ret = sem_init(&sem, 0, count);
5 初始化進程間信號量
pshared不為零時,信號可以由其他進程共享。
#include <semaphore.h>
sem_t sem;
int ret;
int count = 4;
/* to be shared among processes */
ret = sem_init(&sem, 1, count);
6 sem_init返回值
sem_init()在成功完成之后會返回零。其他任何返回值都表示出現(xiàn)了錯誤。如果出現(xiàn)以下任一情況,該函數(shù)將失敗并返回對應的值。
EINVAL
描述:參數(shù)值超過了SEM_VALUE_MAX。
ENOSPC
描述:初始化信號所需的資源已經(jīng)用完。到達信號的SEM_NSEMS_MAX限制。
ENOSYS
描述:系統(tǒng)不支持sem_init()函數(shù)。
EPERM
描述:進程缺少初始化信號所需的適當權(quán)限。
7 增加信號
sem_post語法
int sem_post(sem_t *sem);
#include <semaphore.h>
sem_t sem;
int ret;
ret = sem_post(&sem); /* semaphore is posted */
如果所有線程均基于信號阻塞,則會對其中一個線程解除阻塞。
sem_post返回值
sem_post()在成功完成之后會返回零。其他任何返回值都表示出現(xiàn)了錯誤。如果出現(xiàn)以下情況,該函數(shù)將失敗并返回對應的值。
EINVAL
描述: sem所指示的地址非法。
8 基于信號計數(shù)進行阻塞
使用sem_wait(3RT)可以阻塞調(diào)用線程,直到sem所指示的信號計數(shù)大于零為止,之后以原
子方式減小計數(shù)。
sem_wait語法
int sem_wait(sem_t *sem);
#include <semaphore.h>
sem_t sem;
int ret;
ret = sem_wait(&sem); /* wait for semaphore */
sem_wait返回值
sem_wait()在成功完成之后會返回零。其他任何返回值都表示出現(xiàn)了錯誤。如果出現(xiàn)以下任一情況,該函數(shù)將失敗并返回對應的值。
EINVAL
描述: sem所指示的地址非法。
EINTR
描述:此函數(shù)已被信號中斷。
9 減小信號計數(shù)
使用sem_trywait(3RT)可以在計數(shù)大于零時,嘗試以原子方式減小sem所指示的信號計數(shù)。
sem_trywait語法
int sem_trywait(sem_t *sem);
#include <semaphore.h>
sem_t sem;
int ret;
ret = sem_trywait(&sem); /* try to wait for semaphore*/
此函數(shù)是sem_wait()的非阻塞版本。sem_trywait()在失敗時會立即返回。
sem_trywait返回值
sem_trywait()在成功完成之后會返回零。其他任何返回值都表示出現(xiàn)了錯誤。如果出現(xiàn)以下任一情況,該函數(shù)將失敗并返回對應的值。
EINVAL
描述: sem所指示的地址非法。
EINTR
描述:此函數(shù)已被信號中斷。
EAGAIN
描述:信號已為鎖定狀態(tài),因此該信號不能通過sem_trywait()操作立即鎖定。
10 銷毀信號狀態(tài)
使用sem_destroy(3RT)可以銷毀與sem所指示的未命名信號相關(guān)聯(lián)的任何狀態(tài)。
sem_destroy語法
int sem_destroy(sem_t *sem);
#include <semaphore.h>
sem_t sem;
int ret;
ret = sem_destroy(&sem); /* the semaphore is destroyed */
sem_destroy返回值
sem_destroy()在成功完成之后會返回零。其他任何返回值都表示出現(xiàn)了錯誤。如果出現(xiàn)以
下情況,該函數(shù)將失敗并返回對應的值。
EINVAL
描述: sem所指示的地址非法。
【編輯推薦】