日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Windows事件等待学习笔记(四)—— 事件信号量互斥体

發布時間:2025/3/21 windows 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Windows事件等待学习笔记(四)—— 事件信号量互斥体 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Windows事件等待學習筆記(四)—— 事件&信號量&互斥體

    • 要點回顧
    • 事件
      • 實驗:驗證SignalState
        • 第一步:編譯并運行以下代碼
        • 第二步:觀察結果
        • 第三步:修改代碼并執行
        • 第四步:觀察結果
        • 第五步:修改代碼并執行
        • 第六步:觀察結果
        • 總結
      • 實驗二:驗證Type
        • 第一步:編譯并運行以下代碼
        • 第二步:觀察結果
        • 第三步:修改代碼并執行
        • 第四步:觀察結果
        • 解釋說明
      • 分析WaitForSingleObject
    • 信號量
    • 互斥體
      • 創建互斥體
        • 分析 WaitForSingleObject
      • 釋放互斥體
      • 解決遺棄問題
      • ApcDisable

要點回顧

  • 線程在進入臨界區之前會調用WaitForSingleObject或者WaitForMultipleObjects
  • 此時如果有信號,線程會從函數中退出并進入臨界區;如果沒有信號那么線程將自己掛入等待鏈表,然后將自己掛入等待網,最后切換線程
  • 其它線程在適當的時候,調用方法修改被等待對象的SingleState,設置為有信號(不同的等待對象,會調用不同的函數),并將等待該對象的其它線程從等待鏈表中摘掉,這樣,當前線程便會在WaitForSingleObject或者WaitForMultipleObjects恢復執行(在哪切換就在哪開始執行),如果符合喚醒條件,此時會修改SignalState的值,并將自己從等待網上摘下來,此時的線程才是真正的喚醒
  • 被等待對象不同,主要差異在以下兩點:
  • 不同的被等待對象,修改SingnalState所調用的函數不同
  • 當前線程一旦被臨時喚醒后,會從原來進入等待狀態的地方繼續執行,不同的等待對象,判斷是否符合激活條件修改SignalState的具體操作不同
  • 事件

    創建事件對象API:CreateEvent(NULL, TRUE, FALSE, NULL);

    _DISPATCHER_HEADER+0x000 Type //CreateEvent的第二個參數決定了當前事件對象的類型//TRUE:通知類型對象 FALSE:事件同步對象+0x001 Absolute+0x002 Size+0x003 Inserted+0x004 SignalState //CreateEvent的第三個參數決定了這里是否有值+0x008 WaitListHead

    實驗:驗證SignalState

    第一步:編譯并運行以下代碼

    #include <stdio.h> #include <windows.h>HANDLE g_hEvent;DWORD WINAPI ThreadProc(LPVOID lpParameter) {//當事件變成已通知時WaitForSingleObject(g_hEvent, INFINITE);printf("Thread執行了!\n");return 0; }int main() {//創建事件//默認安全屬性 對象類型 初始狀態 名字g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//設置為有信號//SetEvent(g_hEvent);//創建線程::CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);getchar();return 0; }

    第二步:觀察結果

    無任何輸出,g_hEvent句柄處于無信號狀態

    第三步:修改代碼并執行

    CreateEvent(NULL, FALSE, TRUE, NULL); //第三個參數由FALSE改為TRUE

    第四步:觀察結果

    第五步:修改代碼并執行

    CreateEvent(NULL, FALSE, FALSE, NULL); //第三個參數由TRUE改為FALSE SetEvent(g_hEvent); //新增一行,設置信號量

    第六步:觀察結果

    總結

  • CreateEvent函數的第三個參數決定了事件對象一開始是否有信號
  • CreateEvent函數第三個參數為TRUE時,效果等同于在下一行調用了SetEvent();
  • 實驗二:驗證Type

    第一步:編譯并運行以下代碼

    #include <stdio.h> #include <windows.h>HANDLE g_hEvent;DWORD WINAPI ThreadProc1(LPVOID lpParameter) {//當事件變成已通知時WaitForSingleObject(g_hEvent, INFINITE);printf("Thread1執行了!\n");return 0; }DWORD WINAPI ThreadProc2(LPVOID lpParameter) {//當事件變成已通知時WaitForSingleObject(g_hEvent, INFINITE);printf("Thread2執行了!\n");return 0; }DWORD WINAPI ThreadProc3(LPVOID lpParameter) {//當事件變成已通知時WaitForSingleObject(g_hEvent, INFINITE);printf("Thread3執行了!\n");return 0; }int main() {//創建事件//默認安全屬性 對象類型 初始狀態 名字g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); HANDLE hThread[3];//創建3個線程hThread[0] = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);hThread[1] = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);hThread[2] = ::CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL);//設置事件為已通知SetEvent(g_hEvent);//等待線程結束 銷毀內核對象WaitForMultipleObjects(3, hThread, TRUE, INFINITE);CloseHandle(hThread[0]);CloseHandle(hThread[1]);CloseHandle(hThread[2]);CloseHandle(g_hEvent);getchar();return 0; }

    第二步:觀察結果

    三個線程都得到了執行

    第三步:修改代碼并執行

    g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); //第三個參數由TRUE改為FALSE

    第四步:觀察結果

    只有Thread1得到了執行

    解釋說明

  • CreateEvent第二個參數為TRUE,系統將事件對象的Type設置成0,此時對象為通知類型類型
  • CreateEvent第二個參數為FALSE,系統將事件對象的Type設置成1,此時對象為事件同步對象
  • 當SetEvent函數將信號值(SignalState)設置為1時,如果對象Type0,喚醒所有等待該狀態的線程;如果對象Type1,從鏈表頭找到第一個并喚醒
  • 分析WaitForSingleObject




    總結

  • 當事件對象的Type為0時,WaitForSingleObject函數在處理時并不修改對象的信號量,原來是多少還是多少,因此所有線程都得以執行
  • 當事件對象的Type為1時,WaitForSingleObject函數在處理時對象的信號量清零,因此只有一個線程能夠得到執行
  • 信號量

    描述

  • 事件中,當一個線程進入臨界區時,其它所有事件都無法進入臨界區
  • 信號量允許多個線程進入臨界區
  • 優點:舉個例子,在生產者與消費者的問題中,若生產者只有三份,那么開五個消費者線程是沒有意義的,信號量的存在正是為了解決這種問題

    創建信號量API

    HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, //發放信號量的數量LONG lMaximumCount, //信號量發放數量的最大值LPCTSTR lpName);

    對應內核結構體

    kd> dt _KSEMAPHORE ntdll!_KSEMAPHORE+0x000 Header : _DISPATCHER_HEADER+0x010 Limit : Int4B //IMaximumCount kd> dt _DISPATCHER_HEADER ntdll!_DISPATCHER_HEADER+0x000 Type : UChar //信號量類型為5+0x001 Absolute : UChar+0x002 Size : UChar+0x003 Inserted : UChar+0x004 SignalState : Int4B //lInitialCount+0x008 WaitListHead : _LIST_ENTRY

    釋放信號量API

  • ReleaseSemaphore
  • NtReleaseSemaphore
  • KeReleaseSemaphore
  • 設置SignalState = SignalState + N(參數)
  • 通過WaitListHead找到所有線程,并從等待鏈表中摘除
  • 互斥體

    描述

  • 互斥體(MUTANT)事件(EVENT)信號量(SEMAPHORE) 一樣,都可以用來進行線程的同步控制
  • 但需要注意的是,這幾個對象都是內核對象,這就意味著,通過這些對象可以進行跨進程的線程同步控制,比如:A進程中的X線程B進程中的Y線程都可以控制等待對象Z
  • 極端情況

  • 若B進程的Y線程還沒有來得及調用修改SignalState的函數(如SetEvent),那么等待對象Z將被遺棄,這也就意味著A進程的X線程將永遠等待下去
  • 當遇到這種情況時,事件和信號量可能無法解決,但互斥體可以很好地解決

  • 當構造了一個臨界區一,等待的對象是A,又在臨界區內部構造了一個臨界區二,等待對象為A、B、C三個,當臨界區一執行完自己的功能后,如果等待的對象為事件或者信號量,那么就必須調用相關API將對象設置為有信號,若進入臨界區二前未調用相關API,那么臨界區二將永遠進入等待狀態,這種情況稱為死鎖
    當一個對象需要重復進入臨界區時,若A對象為互斥體,就不會出現死鎖。

    結構體

    nt!_KMUTANT+0x000 Header : _DISPATCHER_HEADER+0x010 MutantListEntry : _LIST_ENTRY+0x018 OwnerThread : Ptr32 _KTHREAD+0x01c Abandoned : UChar+0x01d ApcDisable : UChar

    MutantListEntry:擁有互斥體線程 (KTHREAD+0x010 MutantListHead),是個鏈表頭,圈著當前線程所有的互斥體
    OwnerThread:正在擁有互斥體的線程
    Abandoned:是否已經被放棄不用
    ApcDisable:是否禁用內核APC

    創建互斥體

    函數

    HANDLE CreateMutex( LPSECURITY_ATTRIBUTE SlpMutexAttributes, // 指向安全屬性的指針 BOOL bInitialOwner, // 初始化互斥對象的所有者 LPCTSTR lpName // 指向互斥對象名的指針 );

    API調用流

    CreateMutex -> NtCreateMutant(內核函數) -> KeInitializeMutant(內核函數)

    初始化MUTANT結構體

    MUTANT.Header.Type=2; MUTANT.Header.SignalState=bInitialOwner ? 0 : 1; MUTANT.OwnerThread=bInitialOwner ? 當前線程 : NULL; MUTANT.Abandoned=0; MUTANT.ApcDisable=0;bInitialOwner==TRUE 將當前互斥體掛入到當前線程的互斥體鏈表 (KTHREAD+0x010 MutantListHead)

    分析 WaitForSingleObject


    釋放互斥體

    API

    BOOL WINAPI ReleaseMutex(HANDLE hMutex);

    API執行流

    ReleaseMutex -> NtReleaseMutant -> KeReleaseMutant

    正常調用時

    MUTANT.Header.SignalState++;

    如果SignalState=1(即退出最外圈臨界區后),說明其他進程可以使用了,將該互斥體從線程鏈表中移除

    解決遺棄問題

    描述

  • 當一個進程非正常“死亡時”,系統會調用內核函數MmUnloadSystemImage處理后事
    內核函數MmUnloadSystemImage會調用KeReleaseMutant(X, Y, Abandon, Z),第三個參數用來判斷該互斥體是否被丟棄,正常釋放時值為false,有且只有互斥體有這個待遇
  • if(Abandon == false) //正常調用 {MUTANT.Header.SignalState++; } else {MUTANT.Header.SignalState == 1;MUTANT.OwnerThread == NULL; }if(MUTANT.Header.SignalState==1) //意外結束MUTANT.OwnerThread == NULL; 從當前線程互斥體鏈表中將當前互斥體移除

    ApcDisable

  • 用戶層Mutant
    對應內核函數:NtCreateMutant
    ApcDisable=0
  • 內核層Mutex
    對應內核函數:NtCreateMutex
    ApcDisable=1
  • 注意:若在三環創建互斥體(Mutant),內核APC仍然可以使用;若通過零環創建互斥體(Mutex),那么當前內核APC是被禁止的

    《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

    以上是生活随笔為你收集整理的Windows事件等待学习笔记(四)—— 事件信号量互斥体的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。