1. 程式人生 > >Windows程序同步之訊號量核心物件(Semaphore)

Windows程序同步之訊號量核心物件(Semaphore)

訊號量核心物件主要包括三個部分:使用計數最大資源計數當前資源計數

  • 使用計數:和其他核心物件一樣,用來標識使用該事件物件的不同執行緒個數;
  • 最大資源計數:表示訊號量控制的最大資源的數目;
  • 當前資源計數:表示訊號量當前可用的資源數目;

訊號量使用規則如下:

  1. 如果當前資源計數大於0,訊號量處於觸發狀態;
  2.  如果當前資源計數等於0,那麼訊號量處於未觸發狀態;
  3. 系統絕不會使當前資源計數變為負數;
  4. 系統絕對不會使當前資源計數超過最大資源計數;

在使用訊號量物件時,不要把訊號量物件使用計數和當前資源計數混為一談;

下面是使用訊號量核心物件的所要使用的函式介面:

1CreateSemaphore()

HANDLE WINAPI CreateSemaphore(
  _In_opt_  LPSECURITY_ATTRIBUTES lpEventAttributes,
  _In_      LONG lInitialCount,
  _In_      LONG lMaximumCount,
  _In_opt_  LPCTSTR lpName
);

lpEventAttributes:事件物件的安全屬性,一般置為NULL

lInitialCount:表示一開始可以使用的資源數目,即當前資源計數;

lMaximumCount:訊號量物件可以處理的最大資源數量;

lpName:建立有名的訊號量物件,用於程序間的共享;

如果該訊號量物件已經存在,那麼CreateSemaphore會返回該核心物件的控制代碼,並通過系統返回錯誤ERROR_ALREADY_EXISTS,通過GetLastError()獲得。

2OpenSemaphore()

HANDLE WINAPI OpenSemaphore(
  _In_  DWORD dwDesiredAccess,
  _In_  BOOL bInheritHandle,
  _In_  LPCTSTR lpName
);

dwDesiredAccess:指定想要的訪問許可權,SEMAPHORE_ALL_ACCESS 請求對事件物件的完全訪問,SEMAPHORE_MODIFY_STATE 修改狀態許可權,使用 ReleaseSemaphore函式需要該許可權;

bInheritHandle:是否希望子程序繼承訊號量物件的控制代碼,一般設定為false

lpName:要開啟的訊號量物件的名稱;

其他程序中的執行緒可以通過OpenSemaphoreCreateSemaphore訪問已經存在的訊號量核心物件。和其他核心物件的訪問一樣。

3WaitForSingleObject()

DWORD WINAPI WaitForSingleObject(
  _In_  HANDLE hHandle,
  _In_  DWORD dwMilliseconds
);

hHandle:指向核心物件的控制代碼;

dwMilliseconds:執行緒最大等待多長時間,直到該物件被觸發。經常使用INFINITE,表示阻塞等待。

WaitForSingleObject被稱呼為等待函式,是等待核心物件被觸發通用的等待函式,被用在所有的核心物件觸發等待中。等待函式會檢查訊號量的當前資源使用計數,如果大於0,表示訊號量處於觸發狀態,那麼等待函式會把資源使用計數器減1,並讓呼叫執行緒繼續執行。訊號量的最大優勢就是在於以原子的方式來執行這些測試和設定操作。如果等於0,表示訊號量處於未觸發狀態,那麼系統會讓呼叫執行緒進入等待狀態,直到被喚醒(當前資源計數大於0)。

4ReleaseSemaphore()

BOOL WINAPI ReleaseSemaphore(
  _In_       HANDLE hSemaphore,
  _In_       LONG lReleaseCount,
  _Out_opt_  LPLONG lpPreviousCount
);

hSemaphore:訊號量核心物件的控制代碼;

lReleaseCount:釋放自己使用的資源數目,加到訊號量的當前資源計數上,通常會傳1,當然是根據執行緒使用的資源數目而定。

lpPreviousCount:返回當前資源計數的原始值,應用程式很少會用到這個值,所以一般置為NULL

當一個執行緒使用完訊號量物件控制的有限資源後,應該呼叫ReleaseSemaphore,釋放使用的資源,使訊號量物件的當前資源計數得到恢復。

5CloseHandle()

BOOL WINAPI CloseHandle(
  _In_  HANDLE hObject
);

hObject:指向核心物件的控制代碼

和其他核心物件一樣,無論以什麼方式建立核心物件,我們都必須通過呼叫CloseHandle向系統表明結束使用核心物件。如果傳入的控制代碼有效,系統將獲得核心物件資料結構的地址,並將結構中的使用計數減1,如果使用計數0,就會將核心物件銷燬,從記憶體空間中擦除。

下面是訊號量部分程式碼測試:

#include <iostream>
#include <fstream>
#include <windows.h>
#include <process.h>

using namespace  std;

void semaphoreTest(void *ptr)
{
    int flag = *(int *)ptr;

    HANDLE semaphore = CreateSemaphore(NULL, 2, 2, (LPCWSTR)"streamSemaphore");

    WaitForSingleObject(semaphore, INFINITE);

    ofstream fileStream("c:/test.txt", ios_base::app);

    for (int i = 0; i < 5; ++i)
    {
        Sleep(1000);
        fileStream<<flag;
        fileStream<<' '<<flush;

        //cout<<flag<<' ';
    }

    fileStream.close();

    ReleaseSemaphore(semaphore, 1, NULL);
    CloseHandle(semaphore);
}


int main()
{
    int flag[] = {1, 2, 3};

    for (int i = 0; i < 3; ++i)
    {
        _beginthread(semaphoreTest, 0, &flag[i]);
    }
    
    Sleep(INFINITE);
        
}

程式碼在主執行緒中依次啟動三個執行緒,每個執行緒對同一個檔案進行寫操作,但同一時間最多隻能有兩個執行緒對這個檔案程序操作,所以執行結果如下:
2 1 2 1 2 1 2 1 2 1 3 3 3 3 3

Jun 19, 2013   AM 01:29  @dorm