3.线程等待与唤醒
我們在之前的講解了如何自己實現臨界區以及什么是Windows自旋鎖,這兩種同步方案在線程無法進入臨界區時都會讓當前線程進入等待狀態,一種是通過Sleep函數實現的,一種是通過讓當前的CPU"空轉”實現的,但這兩種等待方式都有局限性:
有沒有更加合理的等待方式呢?只有在條件成熟的時候才將當前線程喚醒?
等待與喚醒機制
在Windows中,一個線程可以通過等待一個或者多個可等待對象,從而進入等待狀態,另一個線程可以在某些時刻喚醒等待這些對象的其他線程。
A線程用WaitForSingleObject 或者 WaitForMultipleObjects進入等待狀態,B線程用SetEvent將線程喚醒,A線程與B線程就是通過 可等待對象 聯系到一起的。
可等待對象
在Windbg中查看如下結構體:
進程 dt KPROCESS 線程 dt KTHREAD 定時器 dt_ KTIMER 信號量 dt KSEMAPHOREI 事件 dt KEVENT 互斥體 dt KMUTANT 文件 dt FILE OBJECT可等待對象都有一個共同的特點,他們的第一個成員都是一個結構體:_DISPATCHER_HEADER。但是有一些結構體并不是比如說文件就不是。
只要結構體中有_DISPATCHER_HEADER就是可等待對象。
可等待對象的差異
WaitForSingleObject(3環)NtWaitorSingleObject(內核) 1)通過3環用戶提供的句柄,找到等待對象的內核地址。 2)如果是以_DISPATCHER-HEADER開頭,直接使用。 3)如果不是以DISPATCHER HEADER開頭的對象,則找到在其中嵌入的_DISPATCHER_HEADER對象。KeWaitForSingleObject(內核) -------核心功能,后面在將測試代碼
#include <stdio.h> #include<windows.h>HANDLE hEvent[2];VOID WINAPI ThreadProc(LPVOID text) {::WaitForSingleObject(hEvent[0], -1);printf("ThreadProc函數執行...\n"); }int main() {hEvent[0] = ::CreateEvent(NULL, TRUE, FALSE, NULL);::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL);getchar();return 0; }windbg中可以通過kd> !process 892ec020 來查看該進程中所有的線程。
還可以通過查看等待鏈表來找到該線程,使用了WaitForSingleObject此線程就會出現在等待鏈表中。
(kd> dt _KTHREAD 895458b8)
WaitBlockList 等待塊,當前線程就是等待塊與被等待塊聯系到一起的。
等待塊中的每個成員的值
kd> dt _KWAIT_BLOCK 0x89545928 nt!_KWAIT_BLOCK+0x000 WaitListEntry : _LIST_ENTRY [ 0x89320290 - 0x89320290 ]+0x008 Thread : 0x895458b8 _KTHREAD //當前線程+0x00c Object : 0x89320288 Void //被等待對象的地址+0x010 NextWaitBlock : 0x89545928 _KWAIT_BLOCK /*單向循環鏈表,如果當前線程只有一個等待對象它指向自己,多個等待對象就是第一個指向第二個,最后一個指向第一個*/+0x014 WaitKey : 0 //當等待塊索引,我這是第0個+0x016 WaitType : 1 /*等待時要求只要有一個等待對象符合條件就可以被激活那么它的值就是1, 如果如果你等待多個對象必須全部符合條件才可以被激活它就是0*/下面來看看一個線程等待多個對象的情況。
#include <stdio.h> #include<windows.h>HANDLE hEvent[2];VOID WINAPI ThreadProc(LPVOID text) {::WaitForMultipleObjects(2, hEvent, FALSE, -1);printf("ThreadProc函數執行...\n"); }int main() {DWORD thread_id_1, thread_id_2;hEvent[0] = ::CreateEvent(NULL, TRUE, FALSE, NULL);hEvent[1] = ::CreateEvent(NULL, TRUE, FALSE, NULL);::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, (LPDWORD)&thread_id_1);getchar();return 0; }windbg中查看情況
+0xc Object指向被等待對象。
被等待對象的 WaitListHead 是一個雙向鏈表,這個鏈表鏈鏈著所有等待塊。
比如說你當前等待的這個對象可能也被別的線程等待著,WaitListHead就把等待它的所有線程的_KWAIT_BLOCK 穿到他的第一個成員中( +0x000 WaitListEntry)。
WaitListHead是鏈表頭它鏈著所有等待塊,掛的位置就是_KWAIT_BLOCK .WaitListEntry。
等待網
如圖:線程1正在等待分發器對象B,線程2正在等待分發器對象A和B。
當對象B變成有信號狀態時,線程1的等待條件已滿足,于是他變成延遲的就緒狀態,以便被調度和執行;
而線程2的等待條件是否滿足,要決取于它的等待類型,KWAIT_BLOCK中的WaitType記錄了線程的等待類型,若是WaitAny則線程2的等待條件滿足;若是WaitAll線程2繼續等待。
當對象A變成有信號狀態時,線程2的等待條件滿足,于是他變成延遲的就緒狀態,以便被調度和執行。
總結:
總結
- 上一篇: 2.自旋锁
- 下一篇: 4.WaitForSingleObjec