理解Semaphore及其用法詳解

Mutex是一把鑰匙,一個人拿了就可進入一個房間,出來的時候把鑰匙交給隊列的第一個。一般的用法是用於串行化對critical section代碼的訪問,保證這段代碼不會被並行的運行。

Semaphore是一件可以容納N人的房間,如果人不滿就可以進去,如果人滿了,就要等待有人出來。對於N=1的情況,稱爲binary semaphore。一般的用法是,用於限制對於某一資源的同時訪問。

Binary semaphoreMutex的差異:

在 有的系統中Binary semaphore與Mutex是沒有差異的。在有的系統上,主要的差異是mutex一定要由獲得鎖的進程來釋放。而semaphore可以由其它進程釋 放(這時的semaphore實際就是個原子的變量,大家可以加或減),因此semaphore可以用於進程間同步。Semaphore的同步功能是所有 系統都支持的,而Mutex能否由其他進程釋放則未定,因此建議mutex只用於保護critical section。而semaphore則用於保護某變量,或者同步。

關於semaphore和mutex的區別,網上有著名的廁所理論(http://koti.mbnet.fi/niclasw/MutexSemaphore.html):

Mutex:Is a key to a toilet. One person can have the key - occupy the toilet - at the time. When finished, the person gives (frees) the key to the next person in the queue.Officially: “Mutexes are typically used to serialise access to a section of re-entrant code that cannot be executed concurrently by more than one thread. A mutex object only allows one thread into a controlled section, forcing other threads which attempt to gain access to that section to wait until the first thread has exited from that section.”
Ref: Symbian Developer Library(A mutex is really a semaphore with value 1.)

Semaphore:

Is the number of free identical toilet keys. Example, say we have four toilets with identical locks and keys. The semaphore count - the count of keys - is set to 4 at beginning (all four toilets are free), then the count value is decremented as people are coming in. If all toilets are full, ie. there are no free keys left, the semaphore count is 0. Now, when eq. one person leaves the toilet, semaphore is increased to 1 (one free key), and given to the next person in the queue.

Officially: “A semaphore restricts the number of simultaneous users of a shared resource up to a maximum number. Threads can request access to the resource (decrementing the semaphore), and can signal that they have finished using the resource (incrementing the semaphore).”
Ref: Symbian Developer Library

所以,mutex就是一個binary semaphore (值就是0或者1)。但是他們的區別又在哪裏呢?主要有兩個方面:

    * 初始狀態不一樣:mutex的初始值是1(表示鎖available),而semaphore的初始值是0(表示unsignaled的狀態)。隨後的操 作基本一樣。mutex_lock和sem_post都把值從0變成1,mutex_unlock和sem_wait都把值從1變成0(如果值是零就等 待)。初始值決定了:雖然mutex_lock和sem_wait都是執行V操作,但是sem_wait將立刻將當前線程block住,直到有其他線程 post;mutex_lock在初始狀態下是可以進入的。
    * 用法不一樣(對稱 vs. 非對稱):這裏說的是“用法”。Semaphore實現了signal,但是mutex也有signal(當一個線程lock後另外一個線程 unlock,lock住的線程將收到這個signal繼續運行)。在mutex的使用中,模型是對稱的。unlock的線程也要先lock。而 semaphore則是非對稱的模型,對於一個semaphore,只有一方post,另外一方只wait。就拿上面的廁所理論來說,mutex是一個鑰 匙不斷重複的使用,傳遞在各個線程之間,而semaphore擇是一方不斷的製造鑰匙,而供另外一方使用(另外一方不用歸還)。

前面的實驗證明,mutex確實能夠做到post和wait的功能,只是大家不用而已,因爲它是“mutex”不是semaphore。


下面給出一個例子:

要 讓一個thread在背景不斷的執行,最簡單的方式就是在該thread執行無窮迴圈,如while(1) {},這種寫法雖可行,卻會讓CPU飆高到100%,因爲CPU一直死死的等,其實比較好的方法是,背景平時在Sleep狀態,當前景呼叫背景時,背景馬 上被喚醒,執行該做的事,做完馬上Sleep,等待前景呼叫。當背景sem_wait()時,就是馬上處於Sleep狀態,當前景sem_post() 時,會馬上換起背景執行,如此就可避免CPU 100%的情形了。

/**//*
    (C) OOMusou 2006 http://oomusou.cnblogs.com

     Filename : pthread_create_semaphore.cpp
     Compiler : gcc 4.10 on Fedora 5 / gcc 3.4 on Cygwin 1.5.21
     Description : Demo how to create thread with semaphorein Linux.
     Release : 12/03/2006
     Compile : g++-lpthread pthread_create_semaphore.cpp
    */
#include <stdio.h> // printf(),
#include <stdlib.h> // exit(), EXIT_SUCCESS
#include <pthread.h> // pthread_create(), pthread_join()
#include <semaphore.h> // sem_init()

sem_t binSem;

void* helloWorld(void* arg);

int main() {
     // Result for System call
    int res = 0;

     // Initialize semaphore
     res = sem_init(&binSem, 0, 0);
    if (res) {
         printf("Semaphore initialization failed!!/n");
         exit(EXIT_FAILURE);
     }

     // Create thread
     pthread_t thdHelloWorld;
     res = pthread_create(&thdHelloWorld, NULL, helloWorld, NULL);
    if (res) {
         printf("Thread creation failed!!/n");
         exit(EXIT_FAILURE);
     }

    while(1) {
         // Post semaphore
         sem_post(&binSem);
         printf("In main, sleep several seconds./n");
        sleep(1);
     }

     // Wait for thread synchronization
     void *threadResult;
     res = pthread_join(thdHelloWorld, &threadResult);
    if (res) {
         printf("Thread join failed!!/n");
         exit(EXIT_FAILURE);
     }

     exit(EXIT_SUCCESS);
}

void* helloWorld(void* arg) {
    while(1) {
         // Wait semaphore
         sem_wait(&binSem);
         printf("Hello World/n");
     }
}

編譯運行:

[root@localhost semaphore]# gcc semaphore.c-lpthread
[root@localhost semaphore]#./a.out
In main, sleep several seconds.
Hello World
In main, sleep several seconds.
Hello World
In main, sleep several seconds.
Hello World
In main, sleep several seconds.
Hello World

 

semaphore

  信號量(Semaphore),有時被稱爲信號燈,是在多線程環境下使用的一種設施, 它負責協調各個線程, 以保證它們能夠正確、合理的使用公共資源。
  什麼是信號量(Semaphore0
  Semaphore分爲單值和多值兩種,前者只能被一個線程獲得,後者可以被若干個線程獲得。
  以一個停車場是運作爲例。爲了簡單起見,假設停車場只有三個車位,一開始三個車位都是空的。這是如果同時來了五輛車,看門人允許其中三輛不受阻礙的進入,然後放下車攔,剩下的車則必須在入口等待,此後來的車也都不得不在入口處等待。這時,有一輛車離開停車場,看門人得知後,打開車攔,放入一輛,如果又離開兩輛,則又可以放入兩輛,如此往復。
  在這個停車場系統中,車位是公共資源,每輛車好比一個線程,看門人起的就是信號量的作用。
  更進一步,信號量的特性如下:信號量是一個非負整數(車位數),所有通過它的線程(車輛)都會將該整數減一(通過它當然是爲了使用資源),當該整數值爲零時,所有試圖通過它的線程都將處於等待狀態。在信號量上我們定義兩種操作: Wait(等待) 和 Release(釋放)。 當一個線程調用Wait等待)操作時,它要麼通過然後將信號量減一,要麼一自等下去,直到信號量大於一或超時。Release(釋放)實際上是在信號量上執行加操作,對應於車輛離開停車場,該操作之所以叫做“釋放”是應爲加操作實際上是釋放了由信號量守護的資源。
  實現
  大家都知道,.Net Framework類庫中提供的線程同步設施包括:
  Monitor, AutoResetEvent, ManualResetEvent,Mutex,ReadWriteLock和 InterLock。 其中 AutoResetEvent, ManualResetEvent,Mutex派生自WaitHandler,它們實際上是封裝了操作系統提供的內核對象。而其它的應當是在.Net虛擬機中土生土長的。顯然來自操作系統內核對象的設施使用起來效率要差一些。不過效率並不是我們這裏要考慮的問題,我們將使用兩個 Monitor 和 一個ManualResetEvent 對象來模擬一個信號量。
  代碼如下:
  public class Semaphore
  {
      private ManualResetEvent waitEvent = new ManualResetEvent(false);
      private object syncObjWait = new object();
      private int maxCount = 1; file://最大資源數
      private int currentCount = 0; file://當前資源數
      public Semaphore()
  {
  }
     public Semaphore( int maxCount )
  {
     this.maxCount = maxCount;
  }
  public bool Wait()
  {
     lock( syncObjWait ) file://只能一個線程進入下面代碼
     {
         bool waitResult = this.waitEvent.WaitOne(); file://在此等待資源數大於零
         if( waitResult )
         {
           lock( this )
             {
               if( currentCount > 0 )
                 {
                    currentCount--;
                    if( currentCount == 0 )
                     {
                         this.waitEvent.Reset();
                     }
                 }
           else
            {
                 System.Diagnostics.Debug.Assert( false, "Semaphore is not allow current count < 0" );
            }
        }
    }
     return waitResult;
   }
  }
  /**//// <summary>
  /// 允許超時返回的 Wait 操作
  /// </summary>
  /// <param name="millisecondsTimeout"></param>
  /// <returns></returns>
  public bool Wait( int millisecondsTimeout )
  {
       lock( syncObjWait ) // Monitor 確保該範圍類代碼在臨界區內
          {
             bool waitResult = this.waitEvent.WaitOne(millisecondsTimeout,false);
            if( waitResult )
            {
               lock( this )
               {
                  if( currentCount > 0 )
                  {
                      currentCount--;
                      if( currentCount == 0 )
                       {
                          this.waitEvent.Reset();
                        }
                    }
                 else
                 {
                       System.Diagnostics.Debug.Assert( false, "Semaphore is not allow current count < 0" );
                  }
               }
            }
        return waitResult;
       }
  }
  public bool Release()
  {
          lock( this ) // Monitor 確保該範圍類代碼在臨界區內
         {
             currentCount++;
             if( currentCount > this.maxCount )
            {
               currentCount = this.maxCount;
               return false;
            }
             this.waitEvent.Set(); file://允許調用Wait的線程進入
          }
        return true;
       }
  }
發佈了41 篇原創文章 · 獲贊 145 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章