天天看點

線程同步方式——信号量

1 信号量

信号量(Semaphore),有時被稱為信号燈,是在多線程環境下使用的一種設施,是可以用來保證兩個或多個關鍵代碼段不被并發調用。

在進入一個關鍵代碼段之前,線程必須擷取一個信号量;一旦該關鍵代碼段完成了,那麼該線程必須釋放信号量。其它想進入該關鍵代碼段的線程必須等待直到第一個線程釋放信号量。為了完成這個過程,需要建立一個信号量VI,然後将Acquire Semaphore VI以及Release Semaphore VI分别放置在每個關鍵代碼段的首末端。确認這些信号量VI引用的是初始建立的信号量。

信号量核心對象用來對資源計數。與其他所有核心對象相同,它們也包含一個使用計數,但它們還包含另外兩個32位值:一個最大資源和一個目前資源計數。

  • 最大資源計數表示信号量可以控制的最大資源;
  • 目前資源計數表示目前可用資源。

信号量的規則如下:

  • 如果目前資源計數大于0,那麼信号量處于觸發狀态
  • 如果目前資源計數等于0,那麼信号量屬于未觸發狀态
  • 系統絕對不會讓目前資源計數變為負數
  • 目前資源計數絕對不會大于最大資源計數

1.1 描述

以一個停車場的運作為例:

  • 假設停車場隻有三個車位(共有資源),一開始三個車位都是空的。
  • 這時如果同時來了五輛車(線程),看門人(信号量)允許其中三輛(線程)直接進入;
  • 然後放下車攔,剩下的車則必須在入口等待,此後來的車也都不得不在入口處等待;
  • 這時,有一輛車(線程)離開停車場,看門人(信号量)得知後,打開車攔,放入外面的一輛進去;
  • 如果又離開兩輛,則又可以放入兩輛,如此往複。

抽象的來講,信号量的特性如下:信号量是一個非負整數(車位數),所有通過它的線程/程序(車輛)都會将該整數減一(通過它使得資源被使用了1個);當該整數值為零時,所有試圖通過它的線程(車輛)都将處于等待狀态。

在信号量上我們定義兩種操作: Wait(等待函數) 和 Release(釋放函數)。當一個線程調用Wait操作時,它要麼得到資源然後将信号量減一,要麼一直等下去(指放入阻塞隊列),直到信号量大于等于1時。Release(釋放)對應于車輛離開停車場,該操作之是以叫做“釋放”是因為釋放了由信号量守護的資源(車位)。

1.2 分類

  • 整型信号量(integer semaphore):信号量是整數
  • 記錄型信号量(record semaphore):每個信号量s,除了一個整數值s.value(計數)外,還有一個程序等待隊列s.L
  • 二進制信号量(binary semaphore):隻允許信号量取0或1值

每個信号量至少須記錄兩個資訊:信号量的值和等待該信号量的程序隊列。

1.3 PV操作

PV操作及信号量的概念都是由荷蘭科學家E.W.Dijkstra提出的。信号量S是一個整數,S大于等于零時代表可供并發程序使用的資源實體數,但S小于零時則S的絕對值表示正在等待使用共享資源的程序數。

◉◉ P操作 申請資源:

  1)S減1;

  2)若S減1後仍大于等于零,則程序繼續執行;

  3)若S減1後小于零,則該程序被阻塞後進入與該信号相對應的隊列中,然後轉入程序排程。

  

◉◉ V操作 釋放資源:

  1)S加1;

  2)若相加結果大于零,則程序繼續執行;

  3)若相加結果小于等于零,則從該信号的等待隊列中喚醒一個等待程序,然後再傳回原程序繼續執行或轉入程序排程。

1.4 說明

信号允許多個線程同時使用共享資源,這與作業系統中的PV操作相同。它指出了同時通路共享資源的線程最大數目。它允許多個線程在同一時刻通路同一資源,但是需要限制在同一時刻通路此資源的最大線程數目。

在用CreateSemaphore()建立信号量時,即要同時指出允許的最大資源計數和目前可用資源計數。一般是将目前可用資源計數設定為最大資源計數,每增加一個線程對共享資源的通路,目前可用資源計數就會減1,隻要目前可用資源計數是大于0的,就可以發出信号量信号。

但是目前可用計數減小到0時則說明目前占用資源的線程數已經達到了所允許的最大數目,不 能在允許其他線程的進入,此時的信号量信号将無法發出。線程在處理完共享資源後,應在離開的同時通過ReleaseSemaphore函數将目前可用資源計數加1。在任何時候目前可用資源計數決不可能大于最大資源計數。

2 信号量包含的幾個操作原語

對信号量有4種操作(#include<semaphore.h>):

  • CreateSemaphore() 建立一個信号量
  • OpenSemaphore() 打開一個信号量
  • ReleaseSemaphore() 釋放信号量
  • WaitForSingleObject() 等待信号量

信号量Semaphore常用有三個函數,使用很友善。下面是這幾個函數的原型和使用說明。

1)CreateSemaphore

  1. 函數功能:建立信号量。
  2. 函數原型:
HANDLE   CreateSemaphore( 
  LPSECURITY ATTRIBUTES  lpSemaphoreAttributes, //安全屬性 
  LONG  lInitialCount, //設定信号量的初始計數
  LONG  lMaximumCount, //設定信号量的最大計數 
  LPCTSTR  lpName       //指定信号量對象的名稱
);      
  1. 參數說明:
    線程同步方式——信号量
  2. 傳回值

    信号量建立成功,将傳回該信号量的句柄。

    如果給出的信号量名是系統已經存在的信号量,将傳回這個已存在信号量的句柄。

    如果失敗,系統傳回NULL,可以調用函數GetLastError()查詢失敗的原因。

2) OpenSemaphore

  1. 函數功能:打開信号量,為現有的一個已命名信号機對象建立一個新句柄。和其他核心對象一樣,信号量也可以通過名字跨程序通路
  2. 函數原型:
HANDLE  OpenSemaphore(
    DWORD  dwDesiredAccess,
    BOOL  bInheritHandle,
    LPCTSTR  lpName
);      
  1. 函數說明:
    線程同步方式——信号量

3) ReleaseSemaphore

  1. 函數功能:遞增信号量的目前資源計數。
  2. 函數原型:
BOOL  ReleaseSemaphore(
  HANDLE  hSemaphore,
  LONG  lReleaseCount, 
  LPLONG  lpPreviousCount 
);      
  1. 函數說明:
    線程同步方式——信号量

5)WaitForSingleObject

  1. 函數功能:WaitForSingleObject函數用來檢測信号狀态,在某一線程中調用該函數時,線程暫時挂起,如果在挂起的dwMilliseconds毫秒内,線程所等待的對象變為有信号狀态,則該函數立即傳回;如果時間已經到達dwMilliseconds毫秒,但hHandle所指向的對象還沒有變成有信号狀态,函數照樣傳回。
  2. 函數原型:
等待一個事件 :
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
等待多個事件 :
DWORD WaitForMultipleObjects(
      DWORD nCount, // 等待句柄數 
      CONST HANDLE *lpHandles, //指向句柄數組 
      BOOL bWaitAll, //是否完全等待标志 
      DWORD dwMilliseconds //等待時間 
    )      
  1. 函數說明:
    線程同步方式——信号量
  2. 傳回值

    執行成功,傳回值訓示出引發函數傳回的事件。它可能為以下值:

    線程同步方式——信号量

5)CloseHandle

信号量的清理與銷毀:

  • 由于信号量是核心對象,是以使用CloseHandle()就可以完成清理與銷毀了。

3 應用和舉例

#include <iostream>
#include "windows.h"  
using namespace std;
DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int number = 1;
HANDLE hSemaphore;  //定義信号量句柄
void main()
{
    HANDLE hThread1;
    HANDLE hThread2;
    hSemaphore = CreateSemaphore(
        NULL,//指定安全屬性,一般傳入NULL
        1,   //指定信号量對象的初始值。該值必須大于等于0
        100, //指出該信号量的最大值,該值必須大于0。
        NULL//給出信号量的名字。
    );      //目前1個資源,最大允許100個同時通路   
    hThread1 = CreateThread(
        NULL, //為NULL則表示傳回的句柄不能被子程序繼承
        0,   //設定初始棧的大小,以位元組為機關,如果為0,那麼預設将使用與調用該函數的線程相同的棧空間大小。
        FunProc1, //指向線程函數的指針
        NULL, //向線程函數傳遞的參數,是一個指向結構的指針,不需傳遞參數時,為NULL。
        0, //控制線程建立的标志,0:表示建立後立即激活
        NULL //儲存新線程的id,是指向線程id的指針,如果為空,線程id不被傳回
    ); //函數成功,傳回線程句柄,否則傳回NULL
    hThread2 = CreateThread(NULL, 0, FunProc2, NULL, 0, NULL);//建立後立即激活
    if (hThread1 != NULL)CloseHandle(hThread1);
    if (hThread2 != NULL)CloseHandle(hThread2);
    Sleep(20000);             // 讓主線程睡眠1秒     
    if (hSemaphore != NULL)CloseHandle(hSemaphore);
 

}
DWORD WINAPI FunProc1(LPVOID lpParameter)
{
    long count;
    while (number<25)
    {
        WaitForSingleObject(hSemaphore, INFINITE);//hSemaphore空閑狀态時,申請該信号量    
        Sleep(1);
        cout << "FunProc 1:" << number << endl;
        ++number;
        ReleaseSemaphore(
            hSemaphore,//信号量句柄
            1,      //目前資源計數上加"1"
            &count //傳回目前資源計數的原始值
        );
    }
    return 0;
}
DWORD WINAPI FunProc2(LPVOID lpParameter)
{
    long count;
    while (number < 25)
    {
        WaitForSingleObject(hSemaphore, INFINITE);//hSemaphore空閑狀态時,申請該信号量     
        Sleep(1);
        cout << "FunProc 2:" << number << endl;
        ++number;
        ReleaseSemaphore(
            hSemaphore,//信号量句柄
            1,      //目前資源計數上加"1"
            &count //傳回目前資源計數的原始值
        );
    }
    
    return 0;
}