1. 程式人生 > >C++多執行緒同步之Semaphore(訊號量)

C++多執行緒同步之Semaphore(訊號量)

一、執行緒間同步的幾種方式

從上篇博文中可以發現,當多個執行緒對同一資源進行使用時,會產生“爭奪”的情況,為了避免這種情況的產生,也就出現了執行緒間的同步這個技術。執行緒間的同步有多種方式,在接下來的博文中我會依次介紹幾種主流的同步方式,以及他們之間的區別。在本篇博文中將介紹使用訊號量Semaphore達到執行緒間同步的目的。老規矩,所有程式碼都講在win32平臺和Linux平臺下都實現一遍。

相關函式和標頭檔案

//標頭檔案
#include <windows.h>

//建立訊號量API
HANDLE WINAPI CreateSemaphore(
 _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//指向SECURITY_ATTRIBUTES的指標;
_In_ LONG lInitialCount, //訊號量物件的初始值; _In_ LONG lMaximumCount, //訊號量物件的最大值,這個值必須大於0; _In_opt_ LPCTSTR lpName //訊號量物件的名稱; ); //等待訊號量API DWORD WINAPI WaitForSingleObject( _In_ HANDLE hHandle, //訊號量物件控制代碼 _In_ DWORD dwMilliseconds //等待訊號量時間,INFINET代表永久等待;
); 返回值: WAIT_ABANDONED(0x00000080L) 表示擁有訊號量的執行緒再終止前未釋放該訊號量; WAIT_OBJECT_0(0x00000000L) 表示等到了訊號量; WAIT_TIMEOUT(0x00000102L) 表示等待超時; WAIT_FAILED((DWORD)0xFFFFFFFF) 表示該函式執行失敗,用GetLastError()得到錯誤碼; //釋放訊號量控制代碼 BOOL WINAPI ReleaseSemaphore( _In_ HANDLE hSemaphore, //訊號量物件控制代碼; _In_ LONG lReleaseCount, //訊號量釋放的值,必須大於0;
_Out_opt_ LPLONG lpPreviousCount //前一次訊號量值的指標,不需要可置為空; ); 返回值:成功返回非0

Win32平臺下原始碼

#include <iostream>
#include <windows.h>
using namespace std;

HANDLE g_hSemaphore = NULL;                             //宣告訊號量變數

unsigned long WINAPI Fun(LPVOID lpParamter)
{
    int iRunTime = 0;
    //執行100次跳出
    while(++iRunTime<100)
    {
        WaitForSingleObject(g_hSemaphore, INFINITE);      //訊號量值-1
        cout << "Fun() is running!"<<endl;
        ReleaseSemaphore(g_hSemaphore, 1, NULL);          //訊號量值+1
        Sleep(10);
    }
    ExitThread(-1);
}

int main()
{
    //建立訊號量物件
    g_hSemaphore = CreateSemaphore(NULL          //訊號量的安全特性
                                  , 1            //設定訊號量的初始計數。可設定零到最大值之間的一個值
                                  , 1            //設定訊號量的最大計數
                                  , NULL         //指定訊號量物件的名稱
                                  );
    if(NULL == g_hSemaphore)
    {
        cout << "create hSemaphore failed! error_code:"<<GetLastError()<<endl;
        return 0;
    }

    int iRunTime = 0;
    unsigned long ulThreadId = 0;
    //建立一個子執行緒
    HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, &ulThreadId);

    //執行100次跳出
    while(++iRunTime<100)
    {
        WaitForSingleObject(g_hSemaphore, INFINITE);   //訊號量值-1
        cout << "main() is running, Thread id is " << ulThreadId <<endl;
        ReleaseSemaphore(g_hSemaphore, 1, NULL);       //訊號量值+1
        Sleep(10);
    }
    system("pause");
    return 0;
}

執行結果:
這裡寫圖片描述
可見未對螢幕資源產生“爭奪”的情況,達到執行緒同步的目的。

Linux平臺

相關函式和標頭檔案

int sem_init(sem_t *sem, int pshared, unsigned int value);
1)pshared==0 用於同一多執行緒的同步;
2)若pshared>0 用於多個相關程序間的同步(即由fork產生的);
int sem_getvalue(sem_t *sem, int *sval);
取回訊號量sem的當前值,把該值儲存到sval中。
若有1個或更多的執行緒或程序呼叫sem_wait阻塞在該訊號量上,該函式返回兩種值:
1) 返回0
2) 返回阻塞在該訊號量上的程序或執行緒數目
linux採用返回的第一種策略。
sem_wait(或sem_trywait)相當於P操作,即申請資源。
int sem_wait(sem_t *sem); // 這是一個阻塞的函式
測試所指定訊號量的值,它的操作是原子的。
若sem>0,那麼它減1並立即返回。
若sem==0,則睡眠直到sem>0,此時立即減1,然後返回;
int sem_trywait(sem_t *sem); // 非阻塞的函式
其他的行為和sem_wait一樣,除了:
若sem==0,不是睡眠,而是返回一個錯誤EAGAIN。
sem_post相當於V操作,釋放資源。
int sem_post(sem_t *sem);
把指定的訊號量sem的值加1;
呼醒正在等待該訊號量的任意執行緒。

原始碼

#include <iostream>
#include <pthread.h>
#include <semaphore.h>
using namespace std;

static sem_t g_semaphore;
static const int g_iRunTime = 5000;

void* Fun(void* ptr)
{
   int iRunTime = 0;
   while(++iRunTime< g_iRunTime)
   {
      sem_wait(&g_semaphore);
      cout<< "Fun() is running!" << endl;
      sem_post(&g_semaphore);
      usleep(100);
    }
}


int main()
{
   pthread_t hHandle;
   sem_init(&g_semaphore, 0, 1);
   int iRet = pthread_create(&hHandle, NULL, Fun, NULL);        //create a thread;
   if(0 != iRet)
   {
       cout << "Create thread failed!" << endl;
   }
   sleep(1);
   int iRunTime = 0;
   while(++iRunTime<g_iRunTime)
   {
      sem_wait(&g_semaphore);
      cout << "main is running!" << endl;
      sem_post(&g_semaphore);
      usleep(100);
   }
   pthread_join(hHandle, NULL);
   return 0;
}

執行結果

這裡寫圖片描述
達到同步效果!

關於Linux訊號量

Linux訊號量比Windows要複雜,上述例子只是使用了其中最常用的一種,還有許多其他種類的訊號量,後期會補上一篇關於Linux訊號量詳解的內容。