首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

多執行緒同步核心:事件物件 完整實戰指南

     編輯:組合語言

事件物件:最簡單的同步原語

事件就像一個**開關**,只有兩種狀態:

  • 有信號 — 開關開啟,等待的執行緒被喚醒繼續執行。
  • 無信號 — 開關關閉,等待的執行緒進入睡眠 (不佔用 CPU)。

適用場景:一對一/一對多通知 (工作完成、啟動信號、停止信號)、閒置等待 替代 Sleep 輪詢。

核心 API

HANDLE CreateEvent(
    LPSECURITY_ATTRIBUTES lpEventAttributes,  ; NULL = 預設安全性
    BOOL bManualReset,                        ; TRUE=手動重設, FALSE=自動重設 (關鍵差異)
    BOOL bInitialState,                       ; TRUE=初始有信號, FALSE=初始無信號
    LPCTSTR lpName                            ; 命名事件 (跨進程共享),NULL=匿名
);

DWORD WaitForSingleObject(
    HANDLE hHandle,   ; 事件代碼
    DWORD dwMilliseconds ; 等待逾時 (INFINITE=無限等待)
);  ; 返回 WAIT_OBJECT_0 (有信號), WAIT_TIMEOUT (逾時), WAIT_FAILED

BOOL SetEvent(HANDLE hEvent);     ; 設為有信號
BOOL ResetEvent(HANDLE hEvent);   ; 設為無信號

自動重設 vs 手動重設 — 關鍵選擇

完整範例:可啟動/停止的計數執行緒

主視窗選單:「啟動執行緒」/「停止執行緒」/「離開」。背景執行緒迴圈計數,收到停止信號彈窗並重置等待。

.386
.model flat, stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_START_THREAD equ 1
IDM_STOP_THREAD  equ 2
IDM_EXIT         equ 3
WM_FINISH        equ WM_USER+100h

.data
ClassName      db "Win32ASMEventClass",0
AppName        db "Win32 ASM Event Example",0
MenuName       db "FirstMenu",0
SuccessString  db "The calculation is completed!",0
StopString     db "The thread is stopped",0
EventStop      BOOL FALSE          ; 全域停止旗標 (簡單同步,實際建議用 InterlockedExchange)

.data?
hInstance      HINSTANCE ?
CommandLine    LPSTR ?
hwnd           HANDLE ?
hMenu          HANDLE ?
ThreadID       DWORD ?
ExitCode       DWORD ?
hEventStart    HANDLE ?           ; 事件物件代碼

.code
start:
    invoke GetModuleHandle, NULL
    mov hInstance, eax
    invoke GetCommandLine
    mov CommandLine, eax
    invoke WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
    invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
    LOCAL wc:WNDCLASSEX, msg:MSG
    mov wc.cbSize, SIZEOF WNDCLASSEX
    mov wc.style, CS_HREDRAW or CS_VREDRAW
    mov wc.lpfnWndProc, OFFSET WndProc
    mov wc.cbClsExtra, NULL
    mov wc.cbWndExtra, NULL
    push hInst; pop wc.hInstance
    mov wc.hbrBackground, COLOR_WINDOW+1
    mov wc.lpszMenuName, OFFSET MenuName
    mov wc.lpszClassName, OFFSET ClassName
    invoke LoadIcon, NULL, IDI_APPLICATION; mov wc.hIcon, eax; mov wc.hIconSm, eax
    invoke LoadCursor, NULL, IDC_ARROW; mov wc.hCursor, eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx, WS_EX_CLIENTEDGE, ADDR ClassName, ADDR AppName, \
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, NULL, NULL, hInst, NULL
    mov hwnd, eax
    invoke ShowWindow, hwnd, SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    invoke GetMenu, hwnd; mov hMenu, eax
    .WHILE TRUE
        invoke GetMessage, ADDR msg, NULL, 0, 0
        .BREAK .IF (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .ENDW
    mov eax, msg.wParam; ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg == WM_CREATE
        ; 1. 建立自動重設、初始無信號的匿名事件
        invoke CreateEvent, NULL, FALSE, FALSE, NULL
        mov hEventStart, eax
        ; 2. 建立背景執行緒
        mov eax, OFFSET ThreadProc
        invoke CreateThread, NULL, NULL, eax, NULL, 0, ADDR ThreadID
        invoke CloseHandle, eax       ; 不需保留執行緒代碼,關閉即可 (執行緒繼續跑)
    .ELSEIF uMsg == WM_DESTROY
        invoke PostQuitMessage, NULL
    .ELSEIF uMsg == WM_COMMAND
        mov eax, wParam
        .if lParam == 0
            .if ax == IDM_START_THREAD
                invoke SetEvent, hEventStart          ; 觸發事件 -> 執行緒喚醒
                invoke EnableMenuItem, hMenu, IDM_START_THREAD, MF_GRAYED
                invoke EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_ENABLED
            .elseif ax == IDM_STOP_THREAD
                mov EventStop, TRUE                   ; 設定停止旗標
                invoke EnableMenuItem, hMenu, IDM_START_THREAD, MF_ENABLED
                invoke EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_GRAYED
            .else
                invoke DestroyWindow, hWnd
            .endif
        .endif
    .ELSEIF uMsg == WM_FINISH
        invoke MessageBox, NULL, ADDR SuccessString, ADDR AppName, MB_OK
    .ELSE
        invoke DefWindowProc, hWnd, uMsg, wParam, lParam
        ret
    .ENDIF
    xor eax, eax; ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
    ; 1. 進入點即等待事件 (無限等待,不佔 CPU)
    invoke WaitForSingleObject, hEventStart, INFINITE
    mov ecx, 600000000          ; 計數迴圈上限

    .WHILE ecx != 0
        .if EventStop != TRUE
            add eax, eax        ; 模擬計算工作
            dec ecx
        .else
            ; 收到停止信號
            invoke MessageBox, hwnd, ADDR StopString, ADDR AppName, MB_OK
            mov EventStop, FALSE
            jmp ThreadProc      ; 重新進入等待 (遞歸跳轉,實際建議用迴圈)
        .endif
    .ENDW

    ; 正常完成
    invoke PostMessage, hwnd, WM_FINISH, NULL, NULL
    invoke EnableMenuItem, hMenu, IDM_START_THREAD, MF_ENABLED
    invoke EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_GRAYED
    jmp ThreadProc              ; 回到等待狀態
    ret
ThreadProc ENDP
end start

程式碼走讀與關鍵點

  1. WM_CREATE 建立事件 + 執行緒:事件預設無信號,執行緒一啟動即在 WaitForSingleObject 睡眠,零 CPU 佔用。
  2. 自動重設:CreateEvent(..., FALSE, FALSE, ...)。SetEvent 喚醒執行緒後,事件自動歸零,**無需手動 ResetEvent**,極適合「單次觸發」語意。
  3. 停止機制:範例使用全域 EventStop 旗標 (簡單但非執行緒安全)。生產代碼必須用 InterlockedExchange 或 Critical Section 讀寫共享旗標。
  4. 執行緒迴圈結構:範例用 jmp ThreadProc 重新進入 (會增長堆疊,長跑會爆棧)。正確寫法:外層無限迴圈包裹 WaitForSingleObject + 工作迴圈。
  5. UI 回饋:執行緒完成/停止時 PostMessage(WM_FINISH) 通知主視窗彈窗,避免在背景執行緒直接呼叫 MessageBox 導致 UI 死鎖/重入問題。

改良版執行緒架構 (推薦)

ThreadProc PROC USES ebx esi edi Param:DWORD
    .WHILE TRUE                          ; 外層無限迴圈:等待啟動 -> 工作 -> 完成/停止 -> 繼續等待
        invoke WaitForSingleObject, hEventStart, INFINITE
        mov ecx, 600000000
        .WHILE ecx != 0
            ; 使用 Interlocked 比較交換檢查停止旗標 (原子操作)
            .if EventStop == 0           ; 簡化寫法,實際用 InterlockedCompareExchange
                add eax, eax
                dec ecx
            .else
                .break                   ; 跳出工作迴圈
            .endif
        .ENDW

        .if EventStop != 0
            invoke PostMessage, hwnd, WM_USER_STOPPED, 0, 0
            mov EventStop, 0
        .else
            invoke PostMessage, hwnd, WM_FINISH, 0, 0
        .endif
        invoke EnableMenuItem, hMenu, IDM_START_THREAD, MF_ENABLED
        invoke EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_GRAYED
    .ENDW
    ret
ThreadProc ENDP

同步物件選型指南

模式bManualReset行為適用場景
自動重設FALSE**單一**等待執行緒被喚醒後,**系統自動**將事件設回無信號。工作項目佇列、單一消費者、避免驚群效應。
手動重設TRUE喚醒**所有**等待執行緒,事件**保持有信號**,直到程式碼呼叫 ResetEvent。廣播通知 (關機、設定變更)、多執行緒同時啟動。
物件語意典型用途
Event狀態通知 (開/關)啟動/停止信號、工作完成通知、跨進程同步 (命名事件)
Mutex互斥擁有權 (遞歸可重入)保護共享資料結構、單一實例應用 (命名 Mutex)
Critical Section輕量級互斥 (進程內、非遞歸)高頻率共享記憶體存取 (比 Mutex 快 10x+)
Semaphore計數資源池連線池、執行緒池限流、生產者/消費者緩衝區
Waitable Timer時間觸發定時任務、週期性喚醒、逾時處理

總結

  • 事件物件是**最輕量、語意最清晰**的「通知」機制。
  • 善用 自動重設 簡化狀態管理 (喚醒即歸零)。
  • 背景執行緒**務必**在等待點 (WaitForSingleObject) 睡眠,避免忙碌等待燒 CPU。
  • 共享旗標/資料**必須**使用原子操作或 Critical Section 保護。
  • UI 互動統一由主執行緒處理 (PostMessage 通知),背景執行緒勿直接操作 UI。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved