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訊號量詳解的內容。