互斥和信號量


轉:http://blog.csdn.net/tietao/article/details/7367827 http://blog.csdn.net/yusiguyuan/article/details/14110437

總結:

互斥用於線程互斥,可以理解為信號量的特例。
信號量可用於線程、進程互斥和同步。

“信號量用在多線程多任務同步的,一個線程完成了某一個動作就通過信號量告訴別的線程,別的線程再進行某些動作(大家都在semtake的時候,就阻塞在 哪里)。而互斥鎖是用在多線程多任務互斥的,一個線程占用了某一個資源,那么別的線程就無法訪問,直到這個線程unlock,其他的線程才開始可以利用這 個資源。比如對全局變量的訪問,有時要加鎖,操作完了,在解鎖。有的時候鎖和信號量會同時使用的”
也就是說,信號量不一定是鎖定某一個資源,而是流程上的概念,比如:有A,B兩個線程,B線程要等A線程完成某一任務以后再進行自己下面的步驟,這個任務 並不一定是鎖定某一資源,還可以是進行一些計算或者數據處理之類。而線程互斥量則是“鎖住某一資源”的概念,在鎖定期間內,其他線程無法對被保護的數據進 行操作。在有些情況下兩者可以互換。

兩者之間的區別:

作用域
信號量: 進程間或線程間(linux僅線程間的無名信號量pthread semaphore)
互斥鎖: 線程間

上鎖時 
信號量: 只要信號量的value大於0,其他線程就可以sem_wait成功,成功后信號量的value減一。若value值不大於0,則sem_wait使得線程阻塞,直到sem_post釋放后value值加一,但是sem_wait返回之前還是會將此value值減一
互斥鎖: 只要被鎖住,其他任何線程都不可以訪問被保護的資源

以下是信號燈(量)的一些概念:

信號燈與互斥鎖和條件變量的主要不同在於”燈”的概念,燈亮則意味着資源可用,燈滅則意味着不可用。如果說后兩中同步方式側重於”等待”操作,即資 源不可用的話,信號燈機制則側重於點燈,即告知資源可用;
沒有等待線程的解鎖或激發條件都是沒有意義的,而沒有等待燈亮的線程的點燈操作則有效,且能保持 燈亮狀態。當然,這樣的操作原語也意味着更多的開銷。

信號燈的應用除了燈亮/燈滅這種二元燈以外,也可以采用大於1的燈數,以表示資源數大於1,這時可以稱之為多元燈。

1. 創建和 注銷

POSIX信號燈標准定義了有名信號燈和無名信號燈兩種,但LinuxThreads的實現僅有無名燈,同時有名燈除了總是可用於多進程之間以外,在使用上與無名燈並沒有很大的區別,因此下面僅就無名燈進行討論。

int sem_init(sem_t *sem, int pshared, unsigned int value)
這是創建信號燈的API,其中value為信號燈的初值,pshared表示是否為多進程共享而不僅僅是用於一個進程。LinuxThreads沒有實現 多進程共享信號燈,因此所有非0值的pshared輸入都將使sem_init()返回-1,且置errno為ENOSYS。初始化好的信號燈由sem變 量表征,用於以下點燈、滅燈操作。

int sem_destroy(sem_t * sem)
被注銷的信號燈sem要求已沒有線程在等待該信號燈,否則返回-1,且置errno為EBUSY。除此之外,LinuxThreads的信號燈 注銷函數不做其他動作。
sem_destroy destroys a semaphore object, freeing the resources it  might  hold.  No  threads  should  be  waiting  on  the
       semaphore  at  the  time  sem_destroy  is  called.  In  the  LinuxThreads implementation, no resources are associated with
       semaphore objects, thus sem_destroy actually does nothing except checking that no thread is waiting on the semaphore.


2. 點燈和滅燈

int sem_post(sem_t * sem)

點燈操作將信號燈值原子地加1,表示增加一個可訪問的資源。

int sem_wait(sem_t * sem)
int sem_trywait(sem_t * sem)

sem_wait()為等待燈亮操作,等待燈亮(信號燈值大於0),然后將信號燈原子地減1,並返回。sem_trywait()為sem_wait()的非阻塞版,如果信號燈計數大於0,則原子地減1並返回0,否則立即返回-1,errno置為EAGAIN。

3. 獲取燈值

int sem_getvalue(sem_t * sem, int * sval)

讀取sem中的燈計數,存於*sval中,並返回0。

4. 其他

sem_wait()被實現為取消點。(取消點事什么意思???)
sem_wait is a cancellation point.
取消點的含義:
當用pthread_cancel()一個線程時,這個要求會被pending起來,當被cancel的線程走到下一個cancellation point時,線程才會被真正cancel掉。
而且在支持原子”比較且交換CAS”指令的體系結構上,sem_post()是唯一能用於異步信號處理函數的POSIX異步信號 安全的API。

On processors supporting atomic compare-and-swap (Intel 486, Pentium and later, Alpha, PowerPC, MIPS  II,  Motorola  68k),
       the  sem_post function is async-signal safe and can therefore be called from signal handlers. This is the only thread syn-
       chronization function provided by POSIX threads that is async-signal safe.

       On the Intel 386 and the Sparc, the current LinuxThreads implementation of sem_post is not async-signal safe  by  lack  of
       the required atomic operations.

 

互斥量(Mutex)

 

互斥量表現互斥現象的數據結構,也被當作二元信號燈。一個互斥基本上是一個多任務敏感的二元信號,它能用作同步多任務的行為,它常用作保護從中斷來的臨界段代碼並且在共享同步使用的資源。

clip_image001

 

Mutex本質上說就是一把鎖,提供對資源的獨占訪問,所以Mutex主要的作用是用於互斥。Mutex對象的值,只有0和1兩個值。這兩個值也分別代表了Mutex的兩種狀態。值為0, 表示鎖定狀態,當前對象被鎖定,用戶進程/線程如果試圖Lock臨界資源,則進入排隊等待;值為1,表示空閑狀態,當前對象為空閑,用戶進程/線程可以Lock臨界資源,之后Mutex值減1變為0。

Mutex可以被抽象為四個操作:

- 創建 Create

- 加鎖 Lock

- 解鎖 Unlock

- 銷毀 Destroy

Mutex被創建時可以有初始值,表示Mutex被創建后,是鎖定狀態還是空閑狀態。在同一個線程中,為了防止死鎖,系統不允許連續兩次對Mutex加鎖(系統一般會在第二次調用立刻返回)。也就是說,加鎖和解鎖這兩個對應的操作,需要在同一個線程中完成。

不同操作系統中提供的Mutex函數:

動作\系統

Win32

Linyx

Solaris

創建

CreateMutex

pthread_mutex_init

mutex_init

加鎖

WaitForSingleObject

pthread_mutex_lock

mutex_lock

解鎖

ReleaseMutex

pthread_mutex_unlock

mutex_unlock

銷毀

CloseHandle

pthread_mutex_destroy

mutex_destroy

 

信號量

信號量(Semaphore),有時被稱為信號燈,是在多線程環境下使用的一種設施, 它負責協調各個線程, 以保證它們能夠正確、合理的使用公共資源。

信號量可以分為幾類:

² 二進制信號量(binary semaphore):只允許信號量取0或1值,其同時只能被一個線程獲取。

² 整型信號量(integer semaphore):信號量取值是整數,它可以被多個線程同時獲得,直到信號量的值變為0。

² 記錄型信號量(record semaphore):每個信號量s除一個整數值value(計數)外,還有一個等待隊列List,其中是阻塞在該信號量的各個線程的標識。當信號量被釋放一個,值被加一后,系統自動從等待隊列中喚醒一個等待中的線程,讓其獲得信號量,同時信號量再減一。

信號量通過一個計數器控制對共享資源的訪問,信號量的值是一個非負整數,所有通過它的線程都會將該整數減一。如果計數器大於0,則訪問被允許,計數器減1;如果為0,則訪問被禁止,所有試圖通過它的線程都將處於等待狀態。

計數器計算的結果是允許訪問共享資源的通行證。因此,為了訪問共享資源,線程必須從信號量得到通行證, 如果該信號量的計數大於0,則此線程獲得一個通行證,這將導致信號量的計數遞減,否則,此線程將阻塞直到獲得一個通行證為止。當此線程不再需要訪問共享資源時,它釋放該通行證,這導致信號量的計數遞增,如果另一個線程等待通行證,則那個線程將在那時獲得通行證。

 

Semaphore可以被抽象為五個操作:

- 創建 Create

- 等待 Wait:

線程等待信號量,如果值大於0,則獲得,值減一;如果只等於0,則一直線程進入睡眠狀態,知道信號量值大於0或者超時。

-釋放 Post

執行釋放信號量,則值加一;如果此時有正在等待的線程,則喚醒該線程。

-試圖等待 TryWait

如果調用TryWait,線程並不真正的去獲得信號量,還是檢查信號量是否能夠被獲得,如果信號量值大於0,則TryWait返回成功;否則返回失敗。

-銷毀 Destroy

信號量,是可以用來保護兩個或多個關鍵代碼段,這些關鍵代碼段不能並發調用。在進入一個關鍵代碼段之前,線程必須獲取一個信號量。如果關鍵代碼段中沒有任何線程,那么線程會立即進入該框圖中的那個部分。一旦該關鍵代碼段完成了,那么該線程必須釋放信號量。其它想進入該關鍵代碼段的線程必須等待直到第一個線程釋放信號量。為了完成這個過程,需要創建一個信號量,然后將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個關鍵代碼段的首末端。確認這些信號量VI引用的是初始創建的信號量。

動作\系統

Win32

POSIX

創建

CreateSemaphore

sem_init

等待

WaitForSingleObject

sem _wait

釋放

ReleaseMutex

sem _post

試圖等待

WaitForSingleObject

sem _trywait

銷毀

CloseHandle

sem_destroy

互斥量和信號量的區別

1. 互斥量用於線程的互斥,信號量用於線程的同步。

這是互斥量和信號量的根本區別,也就是互斥和同步之間的區別。

互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源

2. 互斥量值只能為0/1,信號量值可以為非負整數。

也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多線程互斥問題。信號量可以實現多個同類資源的多線程互斥和同步。當信號量為單值信號量是,也可以完成一個資源的互斥訪問。

3. 互斥量的加鎖和解鎖必須由同一線程分別對應使用,信號量可以由一個線程釋放,另一個線程得到。

 

信號量是最早出現的用來解決進程同步與互斥問題的機制(也可實現進程通信),包括一個稱為信號量的變量及對它進行的兩個原語操作。信號量為一個整數,我們設這個信號量為:sem。很顯然,我們規定在sem大於等於零的時候代表可供並發進程使用的資源實體數,sem小於零的時候,表示正在等待使用臨界區的進程的個數。根據這個原則,在給信號量附初值的時候,我們顯然就要設初值大於零。

p操作和v操作是不可中斷的程序段,稱為原語。P,V原語中P是荷蘭語的Passeren,相當於英文的pass, V是荷蘭語的Verhoog,相當於英文中的incremnet。

且在P,V願語執行期間不允許有中斷的發生。

首先應弄清PV操作的含義:PV操作由P操作原語和V操作原語組成(原語是不可中斷的過程),對信號量進行操作,具體定義如下:

P(S):①將信號量S的值減1,即S=S-1;②如果S>=0,則該進程繼續執行;否則該進程置為等待狀態,排入等待隊列。

V(S):①將信號量S的值加1,即S=S+1;②如果S>0,則該進程繼續執行;否則釋放隊列中第一個等待信號量的進程。

PV操作的意義:我們用信號量及PV操作來實現進程的同步和互斥。PV操作屬於進程的低級通信。

什么是信號量?信號量(semaphore)的數據結構為一個值和一個指針,指針指向等待該 信號量的下一個進程。信號量的值與相應資源的使用情況有關。當它的值大於0時,表示當前可用資源的數量;當它的值小於0時,其絕對值表示等待使用該資源的進程個數。注意,信號量的值僅能由PV操作來改變。

    一般來說,信號量S>=0時,S表示可用資源的數量。執行一次P操作意味着請求分配一個單位資源,因此S的值減1;

當S<0時,表示已經沒有可用資源,請求者必須等待別的進程釋放該類資源,它才能運行下去。而執行一個V操作意味着釋放一個單位資源,因此S的值加1;

若S<=0,表示有某些進程正在等待該資源,因此要喚醒一個等待狀態的進程,使之運行下去。

對信號量有4種操作(include<semaphore>):
1. 初始化(initialize),int set_init(sem_t *sem, int pshared, unsigned int value);//第二參數為0表示進程間不共享
2. 等信號(wait),int sem_wait(sem_t *sem);//信號量大於1時,減一並返回;小於1時線程阻塞。
3. 給信號(signal)int sem_post(sem_t *sem);//信號量加一
4. 清理(destory) int sem_destory(sem_t *sem);

使用信號量實現生產者-消費者例子:

 

#define BUFFER_SIZE 16 // 緩沖區數量  
  
struct prodcons  
{  
    // 緩沖區相關數據結構  
    int buffer[BUFFER_SIZE]; /* 實際數據存放的數組*/  
    int readpos, writepos; /* 讀寫指針*/  
    sem_t empty; /* 緩沖區非空的條件變量 */  
    sem_t occupied; /* 緩沖區未滿的條件變量 */  
    sem_t s_put;/*輸入互斥信號量*/  
    sem_t s_take;/*取出互斥信號量*/  
};  
/* 初始化緩沖區結構 */  
void init(struct prodcons *b)  
{  
    b->readpos = 0;  
    b->writepos = 0;  
    sem_init(&b->empty,0,BUFFER_SIZE);  
    sem_init(&b->occupied,0,0);  
    sem_init(&b->s_put,0,1);//二進制信號量,相當於互斥鎖  
    sem_init(&b->s_take,0,1);  
}  
/* 將產品放入緩沖區,這里是存入一個整數*/  
void put(struct prodcons *b, int data)  
{  
    /*查看空位信號量,是否可以放入產品*/  
    sem_wait(&b->empty);  
    /*查看是否有其他線程正在放入,同一時刻只能一個線程放入*/  
    sem_wait(&b->s_put);  
    /* 寫數據,並移動指針 */  
    b->buffer[b->writepos] = data;  
    b->writepos++;  
    if (b->writepos >= BUFFER_SIZE)  
        b->writepos = 0;  
    sem_post(&b->s_put);//解除互斥  
    sem_post(&b->occupied);//放入完成,被占位置信號量加一  
      
}   
/* 從緩沖區中取出整數*/  
int get(struct prodcons *b)  
{  
    int data;  
    /*查看被占信號量,是否有產品可以拿出*/  
    sem_wait(&b->occupied);  
    /*查看是否有其他線程正在拿出,同一時刻只能一個線程拿出*/  
    sem_wait(&b->s_take);  
    /* 讀數據,移動讀指針*/  
    data = b->buffer[b->readpos];  
    b->readpos++;  
    if (b->readpos >= BUFFER_SIZE)  
        b->readpos = 0;  
    sem_post(&b->s_take);//解除互斥  
    sem_post(&b->empty);//拿出完成,被空位信號量加一  
    return data;  
}  
  
/* 測試:生產者線程將1 到100 的整數送入緩沖區,消費者線 
   程從緩沖區中獲取整數,兩者都打印信息*/  
#define OVER ( - 1)  
struct prodcons buffer;  
void *producer(void *data)  
{  
    int n;  
    for (n = 0; n < 100; n++)  
    {  
        printf("%d --->\n", n);  
        put(&buffer, n);  
    } put(&buffer, OVER);  
    return NULL;  
}  
  
void *consumer(void *data)  
{  
    int d;  
    while (1)  
    {  
        d = get(&buffer);  
        if (d == OVER)  
            break;  
        printf("--->%d \n", d);  
    }  
    return NULL;  
}  
  
int main(void)  
{  
    pthread_t th_a, th_b;  
    void *retval;  
    init(&buffer);  
    /* 創建生產者和消費者線程*/  
    pthread_create(&th_a, NULL, producer, 0);  
    pthread_create(&th_b, NULL, consumer, 0);  
    /* 等待兩個線程結束*/  
    pthread_join(th_a, &retval);  
    pthread_join(th_b, &retval);  
    return 0;  
}  

  

互斥鎖:

  互斥鎖是一種保護機制。上鎖后其他線程不能進入保護區域的代碼,直到鎖被釋放。

信號量:

  信號量是一種同步機制。信號量的值代表可用的資源數目,當值大於0代表有可用資源,則允許繼續操作,否則線程阻塞,等待可用資源。

當可用資源是1時,信號量與互斥鎖基本沒區別,都起保護作用。當資源數大於1,則當信號量大於0時線程都可進行操作。如果資源大於1時使用互斥鎖,則就算資源數大於1時,也只能有一個線程進入操作,其余線程必須阻塞。

信號量可用於進程通信和線程通信,而互斥鎖只能用於線程通信。

    但是還是覺得互斥鎖就可以認為是信號量的特例。

    現在有種突然明白什么是利用信號量進行多線程同步的含義了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM