一、線程間同步的幾種方式
從上篇博文中可以發現,當多個線程對同一資源進行使用時,會產生“爭奪”的情況,為了避免這種情況的產生,也就出現了線程間的同步這個技術。線程間的同步有多種方式,在接下來的博文中我會依次介紹幾種主流的同步方式,以及他們之間的區別。在本篇博文中將介紹使用信號量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信號量詳解的內容。