使用互斥体对象

可以使用 互斥体对象 来保护共享资源免受多个线程或进程的同时访问。 每个线程都必须先获得互斥锁的所有权,然后才能执行访问共享资源的代码。 例如,如果多个线程共享对数据库的访问权限,则线程可以使用互斥体对象一次只允许一个线程写入数据库。

以下示例使用 CreateMutex 函数创建互斥体对象和 CreateThread 函数来创建工作线程。

当该进程中的某个线程向数据库写入数据时,它会首先使用 WaitForSingleObject 函数请求获得互斥体的所有权。 如果线程获取互斥体的所有权,它将写入数据库,然后使用 ReleaseMutex 函数释放其互斥体的所有权。

警告

使用 INFINITE 超时时存在死锁风险: 以下示例将 INFINITE 用作 WaitForSingleObject 的超时参数。 在生产代码中,这意味着如果持有该互斥锁的线程发生死锁、卡住或未能释放该互斥锁,调用线程将被无限期阻塞。 请考虑使用有限的超时值,并通过诊断日志记录实现重试逻辑来检测潜在的死锁。 使用诸如 !handle 调试器扩展(例如 !handle <handle> f)或等待链遍历(GetThreadWaitChain)等工具来诊断互斥锁死锁。

此示例使用结构化异常处理来确保线程正确释放互斥体对象。 __finally 代码块无论 __try 块如何终止都会执行(除非 __try 块包含对 TerminateThread 函数的调用)。 这可以防止互斥体对象无意中被遗弃。

如果互斥体被放弃,则拥有该互斥体的线程在终止前没有正确释放它。 在这种情况下,共享资源的状态不确定,继续使用互斥体可能会掩盖潜在的严重错误。 某些应用程序可能会尝试将资源恢复到一致状态;此示例只是返回一个错误,并停止使用该互斥锁。 有关详细信息,请参阅 互斥体对象

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

#define THREADCOUNT 2

HANDLE ghMutex; 

DWORD WINAPI WriteToDatabase( LPVOID );

int main( void )
{
    HANDLE aThread[THREADCOUNT];
    DWORD ThreadID;
    int i;

    // Create a mutex with no initial owner

    ghMutex = CreateMutex( 
        NULL,              // default security attributes
        FALSE,             // initially not owned
        NULL);             // unnamed mutex

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

    // Create worker threads

    for( i=0; i < THREADCOUNT; i++ )
    {
        aThread[i] = CreateThread( 
                     NULL,       // default security attributes
                     0,          // default stack size
                     (LPTHREAD_START_ROUTINE) WriteToDatabase, 
                     NULL,       // no thread function arguments
                     0,          // default creation flags
                     &ThreadID); // receive thread identifier

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

    // Wait for all threads to terminate

    WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

    // Close thread and mutex handles

    for( i=0; i < THREADCOUNT; i++ )
        CloseHandle(aThread[i]);

    CloseHandle(ghMutex);

    return 0;
}

DWORD WINAPI WriteToDatabase( LPVOID lpParam )
{ 
    // lpParam not used in this example
    UNREFERENCED_PARAMETER(lpParam);

    DWORD dwCount=0, dwWaitResult; 

    // Request ownership of mutex.

    while( dwCount < 20 )
    { 
        dwWaitResult = WaitForSingleObject( 
            ghMutex,    // handle to mutex
            INFINITE);  // no time-out interval
 
        switch (dwWaitResult) 
        {
            // The thread got ownership of the mutex
            case WAIT_OBJECT_0: 
                __try { 
                    // TODO: Write to the database
                    printf("Thread %d writing to database...\n", 
                            GetCurrentThreadId());
                    dwCount++;
                } 

                __finally { 
                    // Release ownership of the mutex object
                    if (! ReleaseMutex(ghMutex)) 
                    { 
                        // Handle error.
                    } 
                } 
                break; 

            // The thread got ownership of an abandoned mutex
            // The database is in an indeterminate state
            case WAIT_ABANDONED: 
                return FALSE; 
        }
    }
    return TRUE; 
}