1. 程式人生 > >秒殺多執行緒第四篇 一個經典的多執行緒同步問題

秒殺多執行緒第四篇 一個經典的多執行緒同步問題

上一篇《秒殺多執行緒第三篇原子操作 Interlocked系列函式》中介紹了原子操作在多程序中的作用,現在來個複雜點的。這個問題涉及到執行緒的同步和互斥,是一道非常有代表性的多執行緒同步問題,如果能將這個問題搞清楚,那麼對多執行緒同步也就打下了良好的基礎。

程式描述:

主執行緒啟動10個子執行緒並將表示子執行緒序號的變數地址作為引數傳遞給子執行緒。子執行緒接收引數 -> sleep(50) -> 全域性變數++ -> sleep(0) -> 輸出引數和全域性變數。

要求:

1.子執行緒輸出的執行緒序號不能重複。

2.全域性變數的輸出必須遞增。

下面畫了個簡單的示意圖:

分析下這個問題的考察點,主要考察點有二個:

1.主執行緒建立子執行緒並傳入一個指向變數地址的指標作引數,由於執行緒啟動須要花費一定的時間,所以在子執行緒根據這個指標訪問並儲存資料前,主執行緒應等待子執行緒儲存完畢後才能改動該引數並啟動下一個執行緒。這涉及到主執行緒與子執行緒之間的同步

2.子執行緒之間會互斥的改動和輸出全域性變數。要求全域性變數的輸出必須遞增。這涉及到各子執行緒間的互斥

下面列出這個程式的基本框架,可以在此程式碼基礎上進行修改和驗證。

  1. //經典執行緒同步互斥問題
  2. #include <stdio.h>
  3. #include <process.h>
  4. #include <windows.h>
  5. long g_nNum; //全域性資源
  6. unsigned int __stdcall Fun(void *pPM); //執行緒函式
  7. const int THREAD_NUM = 10; //子執行緒個數
  8. int main()
  9. {
  10. g_nNum = 0;
  11. HANDLE handle[THREAD_NUM];
  12. int i = 0;
  13. while (i < THREAD_NUM)
  14. {
  15. handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
  16. i++;//等子執行緒接收到引數時主執行緒可能改變了這個i的值
  17. }
  18. //保證子執行緒已全部執行結束
  19. WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
  20. return 0;
  21. }
  22. unsigned int __stdcall Fun(void *pPM)
  23. {
  24. //由於建立執行緒是要一定的開銷的,所以新執行緒並不能第一時間執行到這來
  25. int nThreadNum = *(int *)pPM; //子執行緒獲取引數
  26. Sleep(50);//some work should to do
  27. g_nNum++; //處理全域性資源
  28. Sleep(0);//some work should to do
  29. printf("執行緒編號為%d 全域性資源值為%d\n", nThreadNum, g_nNum);
  30. return 0;
  31. }

執行結果可以參考下列圖示,強烈建議讀者親自試一試。

1

2

3

可以看出,執行結果完全是混亂和不可預知的。本系列將會運用Windows平臺下各種手段包括關鍵段,事件,互斥量,訊號量等等來解決這個問題並作一份全面的總結,敬請關注。