程序描述: 多線程
主線程啓動10個子線程並將表示子線程序號的變量地址做爲參數傳遞給子線程。子線程接收參數 -> sleep(50) -> 全局變量++ -> sleep(0) -> 輸出參數和全局變量。 函數
要求: spa
1.子線程輸出的線程序號不能重複。 線程
2.全局變量的輸出必須遞增。 指針
//經典線程同步互斥問題 #include <stdio.h> #include <process.h> #include <Windows.h> long g_nNum;//全局資源 unsigned int __stdcall Fun(void *pPM);//線程函數 const int THREAD_NUM = 10;//子線程個數 int main() { g_nNum = 0; HANDLE handle[THREAD_NUM]; int i = 0; while(i < THREAD_NUM) { handle[i] = (HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL); i++;//等子線程接收到參數時主線程可能改變了這個i值 } //保證子線程已所有運行結束 WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE); return 0; } unsigned int _stdcall Fun(void *pPM) { //因爲建立線程是要必定開銷的,因此新線程並不能第一時間執行到這 int nThreadNum = *(int *)pPM;//子線程獲取參數 Sleep(50); g_nNum++;//處理全局資源 Sleep(0); printf("線程編號爲%d 全局資源值爲%d\n",nThreadNum,g_nNum); return 0; }執行結果:
//經典線程同步互斥問題(關鍵段=臨界區=critical section) #include <stdio.h>//C標準輸入輸出 #include <process.h> #include <Windows.h> long g_nNum;//全局資源 unsigned int __stdcall Fun(void *pPM);//線程函數 const int THREAD_NUM = 10;//子線程個數 //臨界區變量聲明 CRITICAL_SECTION g_csThreadParameter,g_csThreadCode; int main() { printf("經典線程同步 關鍵段"); //臨界區初始化 InitializeCriticalSection(&g_csThreadParameter); InitializeCriticalSection(&g_csThreadCode); g_nNum = 0; HANDLE handle[THREAD_NUM]; int i = 0; while(i < THREAD_NUM) { EnterCriticalSection(&g_csThreadParameter);//進入子線程序號臨界區 handle[i] = (HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL); i++;//等子線程接收到參數時主線程可能改變了這個i值 } //保證子線程已所有運行結束 WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE); DeleteCriticalSection(&g_csThreadCode); DeleteCriticalSection(&g_csThreadParameter); return 0; } unsigned int _stdcall Fun(void *pPM) { //因爲建立線程是要必定開銷的,因此新線程並不能第一時間執行到這 int nThreadNum = *(int *)pPM;//子線程獲取參數 LeaveCriticalSection(&g_csThreadParameter);//離開子線程序號臨界區 Sleep(50); EnterCriticalSection(&g_csThreadCode);//進入各子線程互斥區域 g_nNum++;//處理全局資源 Sleep(0); printf("線程編號爲%d 全局資源值爲%d\n",nThreadNum,g_nNum); LeaveCriticalSection(&g_csThreadCode);//離開各子線程互斥區 return 0; }運行結果
各子線程已經能夠互斥的訪問與輸出全局資源了,但主線程與子線程之間的同步仍是有點問題。 調試
EnterCriticalSection(&g_csThreadParameter);//進入子線程序號臨界區
主線程和子線程沒能同步:由於主線程能屢次進入這個關鍵區域! code
臨界區會記錄擁有該臨界區的線程句柄(指針),臨界區是有「線程全部權」概念的 對象
typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo;//調試用 LONGLockCount;//n表示有n個線程在等待,初始化爲-1 LONGRecursionCount;//該關鍵段擁有線程對此資源獲取關鍵段的次數 HANDLEOwningThread; //即擁有該關鍵段的線程句柄 from the thread's ClientId->UniqueThread HANDLELockSemaphore;//自復位事件 DWORDSpinCount;//旋轉鎖的設置,單CPU下忽略 } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
所以能夠將臨界區比做旅館的房卡,調用EnterCriticalSection()即申請房卡,獲得房卡後本身固然是能夠屢次進出房間的,在你調用LeaveCriticalSection()交出房卡以前,別人天然是沒法進入該房間。 進程
回到這個經典線程同步問題上,主線程正是因爲擁有「線程全部權」即房卡,因此它能夠重複進入關鍵代碼區域從而致使子線程在接收參數以前主線程就已經修改了這個參數。因此關鍵段能夠用於線程間的互斥,但不能夠用於同步。 事件
在經典多線程問題中設置一個事件和一個臨界區。用事件處理主線程與子線程的同步,用臨界區來處理各子線程間的互斥。
//經典線程同步互斥問題 #include <stdio.h> #include <process.h> #include <Windows.h> long g_nNum;//全局資源 unsigned int __stdcall Fun(void *pPM);//線程函數 const int THREAD_NUM = 10;//子線程個數 //關鍵段和事件聲明 HANDLE g_hThreadEvent; CRITICAL_SECTION g_csThreadCode; int main() { printf("經典線程同步 事件關鍵段"); //事件和關鍵段初始化 自動置位,初始無觸發的匿名事件 g_hThreadEvent = CreateEvent(NULL,FALSE,FALSE,NULL); InitializeCriticalSection(&g_csThreadCode); HANDLE handle[THREAD_NUM]; g_nNum = 0; int i = 0; while(i < THREAD_NUM) { handle[i] = (HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL); WaitForSingleObject(g_hThreadEvent,INFINITE);//等待事件被觸發 i++;//等子線程接收到參數時主線程可能改變了這個i值 } WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE); CloseHandle(g_hThreadEvent); DeleteCriticalSection(&g_csThreadCode); return 0; } unsigned int _stdcall Fun(void *pPM) { //因爲建立線程是要必定開銷的,因此新線程並不能第一時間執行到這 int nThreadNum = *(int *)pPM;//子線程獲取參數 SetEvent(g_hThreadEvent);//觸發事件 Sleep(50); EnterCriticalSection(&g_csThreadCode);//進入各子線程互斥區域 g_nNum++;//處理全局資源 Sleep(0); printf("線程編號爲%d 全局資源值爲%d\n",nThreadNum,g_nNum); LeaveCriticalSection(&g_csThreadCode);//離開各子線程互斥區 return 0; }
說明主線程與子線程達到了同步。
最後總結下事件Event
1.事件是內核對象,事件分爲手動置位事件和自動置位事件。事件Event內部它包含一個使用計數(全部內核對象都有),一個布爾值表示是手動置位事件仍是自動置位事件,另外一個布爾值用來表示事件有無觸發。
2.事件能夠由SetEvent()來觸發,由ResetEvent()來設成未觸發。還能夠由PulseEvent()來發出一個事件脈衝。
3.事件能夠解決線程間同步問題,所以也能解決互斥問題。
互斥量
//經典線程同步互斥問題 #include <stdio.h> #include <process.h> #include <Windows.h> long g_nNum;//全局資源 unsigned int __stdcall Fun(void *pPM);//線程函數 const int THREAD_NUM = 10;//子線程個數 //關鍵段和互斥量 HANDLE g_hThreadParameter; CRITICAL_SECTION g_csThreadCode; int main() { printf("經典線程同步 事件關鍵段"); //互斥兩和關鍵段初始化 第二個參數爲true表示互斥量爲建立線程全部 g_hThreadParameter= CreateMutex(NULL,FALSE,NULL); InitializeCriticalSection(&g_csThreadCode); HANDLE handle[THREAD_NUM]; g_nNum = 0; int i = 0; while(i < THREAD_NUM) { handle[i] = (HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL); WaitForSingleObject(g_hThreadParameter,INFINITE);//等待互斥量被觸發 i++;//等子線程接收到參數時主線程可能改變了這個i值 } WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE); CloseHandle(g_hThreadParameter); DeleteCriticalSection(&g_csThreadCode); for(i = 0;i<THREAD_NUM;i++) CloseHandle(handle[i]); return 0; } unsigned int _stdcall Fun(void *pPM) { //因爲建立線程是要必定開銷的,因此新線程並不能第一時間執行到這 int nThreadNum = *(int *)pPM;//子線程獲取參數 ReleaseMutex(g_hThreadParameter);//觸發互斥量 Sleep(50); EnterCriticalSection(&g_csThreadCode);//進入各子線程互斥區域 g_nNum++;//處理全局資源 Sleep(0); printf("線程編號爲%d 全局資源值爲%d\n",nThreadNum,g_nNum); LeaveCriticalSection(&g_csThreadCode);//離開各子線程互斥區 return 0; }與關鍵段相似,互斥量也是不能解決線程間的同步問題。
最後總結下互斥量Mutex:
1.互斥量是內核對象,它與關鍵段都有「線程全部權」因此不能用於線程的同步。
2.互斥量可以用於多個進程之間線程互斥問題,而且能完美的解決某進程意外終止所形成的「遺棄」問題。
信號量Semaphore
因爲信號量是內核對象,所以使用CloseHandle()就能夠完成清理與銷燬了。