Mutex Objects

A mutex (short for mutual exclusion) is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads. A thread acquires the mutex before accessing the shared data and releases the mutex after it has finished accessing the data. If another thread tries to acquire the mutex while it is held by another thread, the second thread will block until the mutex is released.

Overview

Mutexes are fundamental to concurrent programming in Windows. They ensure that critical sections of code, which operate on shared resources, are executed by only one thread at a time, preventing race conditions and data corruption.

Key characteristics of mutexes:

Creating and Opening Mutexes

Mutexes can be created or opened using the following functions:

CreateMutex Function

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);

This function creates a new mutex object or opens an existing one if the specified name already exists.

Return Value: If the function succeeds, the return value is a handle to the newly created or opened mutex object. If the function fails, the return value is NULL.

OpenMutex Function

HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);

This function opens an existing named mutex object.

Return Value: If the function succeeds, the return value is a handle to the mutex object. If the function fails, the return value is NULL.

Using Mutexes

Once a mutex handle is obtained, threads can interact with it using several functions:

WaitForSingleObject

This is the primary function for acquiring a mutex. It waits until the specified object is in the signaled state or the time-out interval elapses.

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

Return Value: WAIT_OBJECT_0 if the function returns because the specified object is signaled; WAIT_TIMEOUT if the time-out interval elapses. Other return values indicate an error.

ReleaseMutex

This function releases ownership of a mutex object. The calling thread must own the mutex.

BOOL ReleaseMutex(HANDLE hMutex);

Return Value: TRUE if the function succeeds, FALSE otherwise.

Example Scenario

Consider a scenario where multiple threads need to update a shared counter. A mutex can protect this operation.


#include <windows.h>
#include <iostream>

HANDLE hMutex;
int sharedCounter = 0;

DWORD WINAPI UpdateCounterThread(LPVOID lpParam) {
    WaitForSingleObject(hMutex, INFINITE); // Acquire the mutex

    // Critical Section: Only one thread can execute this at a time
    sharedCounter++;
    std::cout << "Thread " << GetCurrentThreadId() << ": Counter = " << sharedCounter << std::endl;
    // End of Critical Section

    ReleaseMutex(hMutex); // Release the mutex
    return 0;
}

int main() {
    hMutex = CreateMutex(NULL, FALSE, NULL); // Create a mutex

    if (hMutex == NULL) {
        std::cerr << "CreateMutex failed: " << GetLastError() << std::endl;
        return 1;
    }

    HANDLE hThread1 = CreateThread(NULL, 0, UpdateCounterThread, NULL, 0, NULL);
    HANDLE hThread2 = CreateThread(NULL, 0, UpdateCounterThread, NULL, 0, NULL);

    WaitForSingleObject(hThread1, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);

    CloseHandle(hThread1);
    CloseHandle(hThread2);
    CloseHandle(hMutex); // Close the mutex handle

    std::cout << "Final Counter Value: " << sharedCounter << std::endl;
    return 0;
}
        

Considerations

Note on Mutex vs. Critical Section

Mutexes are kernel objects and can be used for synchronization between processes. Critical sections are lighter-weight synchronization objects that can only be used for synchronization between threads within the same process. Mutexes involve context switching and have more overhead than critical sections.

Related Topics