Windows同步技术-等待多个对象(一)

多对象等待的上限

在 Windows 平台下,使用 WaitForMultipleObjects 函数等待多个同步对象(如事件、互斥体、信号量等)时,最多可以同时等待 64 个对象。这一限制由 Windows API 中的常量 MAXIMUM_WAIT_OBJECTS(定义为 64)明确规定。

同时Windows单个进程至少可以创建 16384 个句柄,这是由内核内存池大大小决定的,所以可能有需要等待数百个事件对象的场景发生,如何突破这个限制呢?

第一种方案是创建一个线程来等待 MAXIMUM_WAIT_OBJECTS 句柄,然后等待该线程和其他句柄。 使用此技术可将句柄分解为 MAXIMUM_WAIT_OBJECTS组,这个方案中,两层结构可以保证快速响应的前提下等待 3696 个句柄,而它仅仅比调用WaitForMultipleObjects 多了几个内核切换的动作。

第二种方案则是调用 RegisterWaitForSingleObject 或 SetThreadpoolWait 等待每个句柄。 线程池在句柄上高效等待,并在发出对象信号或超时间隔过期后分配工作线程,本质上它和第一个方案是一回事,只是使用了线程池进行调度罢了。

这个时间仍旧是可以计算的,假设CPU主频是3GHz,一个周期大约是0.33纳秒,1微秒就是大约3000个周期。不过这可能是一个平均估计,而最优情况可能更低。我们可以大概计算一下:

1 模式切换(用户态 ↔ 内核态)

通过系统调用(如syscall或sysenter指令)进入内核态的开销通常在 100~300个时钟周期(假设现代CPU主频为3GHz)。这一过程主要涉及寄存器保存和权限级别的切换,若数据在缓存中,开销可能更低。

2 上下文切换(线程切换)

如果WaitForSingleObject导致线程阻塞并触发调度器切换线程,则需要完整的上下文切换。这包括保存当前线程的寄存器、程序计数器、栈指针等状态,并加载下一个线程的上下文。

在最优情况下,如线程属于同一进程,数据在缓存中,无需TLB刷新,开销可能在 1000~3000个时钟周期。跨进程切换或缓存未命中时,开销可能显著增加,例如达到数万周期。

所以综合考虑,若仅执行系统调用且无需阻塞,例如等待对象已就绪,开销可能接近模式切换的最小值,约100~300周期;若线程被阻塞并切换,则总开销至少需要 1000周期以上(取决于上下文保存/恢复和调度逻辑)。

代码演示

以下示例使用 CreateEvent 函数创建两个事件对象,使用 CreateThread 函数创建线程。 然后,它使用 WaitForMultipleObjects 函数等待线程将其中一个对象的状态设置为使用 SetEvent 函数发出信号。

#include <windows.h>
#include <stdio.h>

HANDLE ghEvents[2];

DWORD WINAPI ThreadProc( LPVOID );

int main( void )
{
    HANDLE hThread; 
    DWORD i, dwEvent, dwThreadID; 

    // Create two event objects

    for (i = 0; i < 2; i++) 
    { 
        ghEvents[i] = CreateEvent( 
            NULL,   // default security attributes
            FALSE,  // auto-reset event object
            FALSE,  // initial state is nonsignaled
            NULL);  // unnamed object

        if (ghEvents[i] == NULL) 
        { 
            printf("CreateEvent error: %d\n", GetLastError() ); 
            ExitProcess(0); 
        } 
    } 

    // Create a thread

    hThread = CreateThread( 
                 NULL,         // default security attributes
                 0,            // default stack size
                 (LPTHREAD_START_ROUTINE) ThreadProc, 
                 NULL,         // no thread function arguments
                 0,            // default creation flags
                 &dwThreadID); // receive thread identifier

    if( hThread == NULL )
    {
        printf("CreateThread error: %d\n", GetLastError());
        return 1;
    }

    // Wait for the thread to signal one of the event objects

    dwEvent = WaitForMultipleObjects( 
        2,           // number of objects in array
        ghEvents,     // array of objects
        FALSE,       // wait for any object
        5000);       // five-second wait

    // The return value indicates which event is signaled

    switch (dwEvent) 
    { 
        // ghEvents[0] was signaled
        case WAIT_OBJECT_0 + 0: 
            // TODO: Perform tasks required by this event
            printf("First event was signaled.\n");
            break; 

        // ghEvents[1] was signaled
        case WAIT_OBJECT_0 + 1: 
            // TODO: Perform tasks required by this event
            printf("Second event was signaled.\n");
            break; 

        case WAIT_TIMEOUT:
            printf("Wait timed out.\n");
            break;

        // Return value is invalid.
        default: 
            printf("Wait error: %d\n", GetLastError()); 
            ExitProcess(0); 
    }

    // Close event handles

    for (i = 0; i < 2; i++) 
        CloseHandle(ghEvents[i]); 
    
    return 0;   
}

DWORD WINAPI ThreadProc( LPVOID lpParam )
{

    // lpParam not used in this example
    UNREFERENCED_PARAMETER( lpParam);

    // Set one event to the signaled state

    if ( !SetEvent(ghEvents[0]) ) 
    {
        printf("SetEvent failed (%d)\n", GetLastError());
        return 1;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员王马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值