1、使用MsgWaitForMultipleObjects或WaitForMultipleObjects時注意:
其第一個參數最大值64
即等待的線程最多不超過64個
錯誤:等待超過64個線程,返回WAIT_FAILED,參數無效
2、每個線程都有獨有的寄存器,線程併發時:
變量variable = 0
線程A讀取變量值variable到寄存器eax,eax進行加1,將eax中值寫入變量。總共執行了3步
如果線程B在A執行的第一步後,第三步未結束前。那麼B讀取的variable還是0
此時A寫入variable的值爲1,B寫入variable的值也爲1.多線程的兩次自加結果只加1。
3、錯誤筆記
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
臨界區崩潰
原因:在多線程尚未結束時,就調用DeleteCriticalSection刪除了臨界區
DWORD g_taskCnt = 0;
InterlockedIncrement(&g_taskCnt);
InterlockedDecrement(&g_taskCnt);
實現數值得原子性加減,保證一個線程訪問變量時,其它線程不能訪問
4、內核對象
內核對象都是進程相關的,除特定情況,不同進程間的句柄是不能相互使用的。
跨越進程邊界共享內核對象:
方法:
子進程繼承對象句柄
給對象命名(採用GUID來命名,以確保不會與其它公司的程序的對象名相同)
複製對象句柄,DuplicateHandle
文件共享內存,兩個進程間共享數據塊。
郵箱或管道,聯網的運行進程間發送數據塊。
互斥對象、信標、事件,不同進程間的線程能夠同步運行。
進程內核對象與進程地址空間是兩個不同的概念。
進程內核對象維護關於進程的統計信息。
(即使進程關閉,只要內核對象未撤銷就可獲取進程的統計信息,如GetExitCodeProcess)
進程地址空間加載代碼,運行。
5、CreateProcess創建進程
創建的進程和主線程會被分別賦予一個獨一無二的ID號。
但是,ID號可能被複用,即進程結束後,新的進程可能與之前進程ID相同。
父進程中在獲取子進程句柄後,使用CloseHandle之前(保證內核對象未被撤銷),ID都標識子進程
進程終止
1、主線程的進入點函數返回(最好使用這個方法)
保證所有線程資源得到清除的唯一方法
? 該線程創建的任何C + +對象將能使用它們的析構函數正確地撤消。
? 操作系統將能正確地釋放該線程的堆棧使用的內存。
? 系統將進程的退出代碼(在進程的內核對象中維護)設置爲進入點函數的返回值。
? 系統將進程內核對象的返回值遞減1
2、進程中的一個線程調用ExitProcess函數(應該避免使用這種方法)
3、另一個進程中的線程調用TerminateProcess函數(應該避免使用這種方法)
進程被終止,沒有機會執行自己的清除操作
但是,一旦進程終止運行(無論採用何種方法),系統將確保該進程不會將它的任何部分
遺留下來。
4、進程中的線程自行終止(這種情況幾乎從未發生)
進程終止時出現的情況:
(1)進程中所有的線程全部終止
(2)進程指定的所有用戶對象和GDI對象均被釋放,
所有內核對象被關閉(如果沒有其它進程打開它們的句柄,那麼這些內核對象將被撤銷,
但是,如果其它進程打開了它們的句柄,內核對象的引用計數會減1,但不撤銷,直到內核對象的引用計數爲0)
(3)進程的退出代碼將從STILL_ACTIVE改爲傳遞給ExitProcess或TerminateProcess的代碼
(4)進程內核對象的狀態變成收到通知的狀態,系統中其它線程可以掛起,直到進程終止運行
(5)進程內核對象的引用計數減1
句柄表依賴進程存在,句柄表中保存了內核對象的地址,而程序代碼中獲取的句柄都是句柄表中的索引值
進程是不活潑的,從來不執行任何東西,它只是線程的容器,線程在進程環境中創建,在進程地址空間中執行代碼、數據操作
6、線程
每個進程至少需要一個線程
CreateTHread
_beginthreadex,C++運行期庫函數
組成:
(1)線程內核對象(線程統計信息組成的一個小型數據結構),操作系統用它來實施線程管理,內核對象也是系統用來存放線程統計信息的地方。
(2)線程堆棧,用於維護線程在執行代碼時需要的所有函數參數和局部變量
每當進程初始化時,其主線程與C/C++運行期庫的啓動代碼一道開始啓動,調用進入點函數mian,
繼續運行直到進入點函數返回並且C/C++運行期庫調用ExitProcess爲止。
終止線程的運行:
(1)線程函數返回
(2)調用ExitThread函數,線程自行撤銷
(3)同一個進程或另一個進程中的線程調用TerminatedThread函數
(4)包含線程的進程終止
線程返回,可保證:
(1)線程函數中創建的所有C++對象均將通過它們的撤銷函數正確的撤銷
(2)操作系統將正確的釋放線程堆棧使用的內存
(3)系統將線程的退出代碼(在線程程的內核對象中維護),從STILL_ACTIVE改爲線程函數的返回值
(4)系統將遞減線程內核對象的使用計數
ExitThread結束:
終止線程的運行,並導致操作系統清除該線程使用的所有操作系統資源,但是C++資源(如C++類對象)將不會被撤銷。
ExitThread是VC++獨有,其它平臺不支持,因此最好使用C++運行期庫函數替代_endthreadex
GetExitcodeThread獲取線程退出代碼,檢查線程狀態
_endthred與_endthreadex區別
_endthred會調用CloseHandle,因此如果調用_endthred退出線程後,
再調用GetExitCodeThread(hThread, &dwExitCode)都會失敗
內核對象:
進程的句柄表中保存內核對象的地址,因此句柄是內核對象地址在當前進程中的一個索引值。
僞句柄:
GetCurrentProcess和GetCurrentThread獲取當前進程或線程的內核對象句柄,但是獲取的只是僞句柄
通過僞句柄操作,不會對內核對象的使用計數產生任何影響,例如:CloseHandle時,參數是僞句柄,則忽略並返回FALSE
DuplicateHandle可用於獲取複製獲取真實的句柄,但是獲取到的句柄會遞增內核對象的使用計數,因此在使用了該句柄後需要CloseHandle
暫停和恢復:
創建進程、線程時,可傳遞CREATE_SUSPENDED標誌,那麼進程或線程初始化成功之後們就會將暫停計數設置爲1.
ResumeThread恢復線程
SuspendThread暫停線程(線程想要運行,那麼暫停了幾次就需要恢復幾次)
Sleep(0)放棄當前CPU時間片
Windows線程同步:
用戶方式的線程同步機制
1、互鎖函數
範圍:共享變量或共享內存內的變量。
原子性:保證多線程在同一時刻只能有一個線程獲得對該同步變量的操作權限。
InterlockedExchangeAdd
InterlockedIncrement、InterlockedDecrement
InterlockedExchange
InterlockedExchangePointer
InterlockedCompareExchange
InterlockedCompareExchangePointer
2、關鍵代碼段(臨界區)
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
當線程試圖進入另一個線程擁有的關鍵代碼段時,調用線程就立即被至於等待狀態,
意味着該線程從用戶方式轉換爲內核方式(大約1000個CPU週期)。
因此爲了提高代碼段的運行性能,可在進入等待之前先用循環鎖進行循環,以便設法多次取得該資源,
只有每一次都失敗時,該線程才轉入內核方式進行等待。
(單處理器時循環置爲0)
InitializeCriticalSectionAndSpinCount(&cs, 4000)//第二個參數是循環的次數
SetCriticalSectionSpinCount設置循環次數
內核方式的同步機制:
內核機制的適應性遠遠優於用戶方式,但是調用線程從用戶方式轉爲內核方式速度緩慢,需要大約1000個CPU週期
等待函數,等待對象重置到通知狀態
WaitForSingleObject(hProcess, INFINITE)
返回值:WAIT_OBJECT_0/WAIT_TIMEOUT/WAIT_FAILED
WaitForMultipleObject
事件內核對象:
CreateEvent
OpenEvent
SetEvent
ResetEvent
事件運行時處於未通知狀態,結束時處於通知狀態。
人工重置的事件:人工重置的事件得到通知時,等待該事件的所有線程均變爲可調度線程。需要自己調度ResetEvent
自動重置的事件:等待該事件的線程中只有一個線程變爲可調度線程。
(自動重置事件副作用,當線程成功等待到該對象時,自動重置的事件就會自動重置到未通知狀態)
等待定時器內核對象:(用戶定時器,使用SetTimer進行設置)
在某個時間或按規定的間隔時間發出自己的信號通知的內核對象。
CreateWaitableTimer
OpenWaitableTimer
SetWaitableTimer(最後一個參數用於確定,當定時器時間到來時,是否喚醒暫停的計算機)
信標內核對象:
用於對資源進行計數。
CreateSemaphore
OpenSemaphore
ReleaseSemaphore增加當前資源數量
互斥對象內核對象:
確保線程擁有對單個資源的互斥訪問權。
(互斥對象特性與關鍵代碼段相同,但是互斥對象屬於內核對象,關鍵代碼段屬於用戶對象。
這也就意味着,互斥對象的運行速度比關鍵代碼段慢,但是不同進程的多線程能夠訪問單個互斥對象,
並且線程在等待資源訪問時可以設定一個超時值)
CreateMutex
OpenMutex
ReleaseMutex釋放互斥對象
失敗收穫:
互斥對象不同於其它內核對象,擁有“線程所有權”,保持對互斥對象和線程對象的跟蹤,本線程擁有的互斥對象只有本線程才能釋放。
並且,若本線程結束時未釋放互斥對象,那麼該互斥對象視爲被遺棄,返回WAIT_ABANDONED而非WAIT_OBJECT_0.
“線程所有權”,互斥對象對等待到它的隨想保持跟蹤
若佔用互斥量的對象在釋放互斥量之前終止,系統會認爲互斥量被遺棄,因爲佔用它的線程被終止,因此無法釋放它。
因爲系統會記錄所有的互斥量和線程內核對象,因此它確切的知道互斥量何時被遺棄。當互斥量被遺棄的時候,
系統會自動將互斥量的線程ID設爲0,將它的遞歸計數設爲0。然後系統檢查有沒有其他線程正在等待該互斥量。
如果有,那麼系統會公平的選擇一個正在等待的線程,把對象內部的線程Id設爲所選擇的那個線程的線程ID,
並將遞歸計數設爲1,這樣被選擇的線程就變成可調度狀態了。
一旦檢測到某互斥量被檢測到,則WaitForSingleObject返回的不是WAIT_OBJECT_0,而是一個特殊值WAIT_ABANDONED。
返回該值,說明等待的互斥量被某個線程遺棄,同時說明被保護的資源已經被破壞了。
這種情況下,寫的程序自己必須決定該怎麼做。
同步互斥不一樣:
同步:一個線程需要等待另一個線程完成後再運行,是有序的。
互斥:多個線程競爭一個資源,是無序的。
相似的地方:
同一時間只能有一個線程佔有該資源。
關鍵字段和互斥鎖,因爲“線程所有權”,所以只能在同一個線程中釋放。只能用於互斥。
信號量、事件,可以在不同線程中釋放,因此不僅可用於互斥,也可用於同步。