Search code examples
c++winapimutex

How to use Windows.h Mutexes with two threads


I'm facing a problem using mutexes in two different threads.

I have two thread functions: a producer, which receives data from the user and writes it to a global variable, and a consumer, which receives this data from a global variable, processes it and returns it to the user.

Therefore, I created two mutexes: "Information generated" and "Information processed". The first is in the “open” state when information has been received from the user and has already been recorded in a global variable, and in the “closed” state when this has not yet happened. The second is in the “open” state when the information is processed and displayed to the user, and in the “closed” state when this has not yet happened.

Accordingly, the producer thread waits for the "Information processed" mutex to open to begin receiving new information from the user. When the information is received, the producer thread opens the "Information generated" mutex.

In turn, the consumer thread waits for the “Information generated” mutex to open in order to begin processing it. When the information is processed, the consumer thread opens the "Information Processed" mutex.

However! When I run this code, the consumer thread stands waiting, the producer thread runs non-stop, and the consumer thread does not start its work.

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

HANDLE hMutexReady,   // Mutex "information generated"
       hMutexHandled, // Mutex "information processed"

// Producer thread function
DWORD WINAPI producer(LPVOID param) {
    while (true) {
        // Capture the "information processed" mutex    
        WaitForSingleObject(hMutexHandled, INFINITE);

        // Get some data from user and store it in global variable

        // Release the "information generated" mutex
        ReleaseMutex(hMutexReady);
    }
    return 0;
}

// Consumer thread function
DWORD WINAPI consumer(LPVOID param) {
    while (true) {
        // Caprute the "information generated" mutex
        WaitForSingleObject(hMutexReady, INFINITE);

        // Get data from global variable, process it and return to user

        // Release the "information processed" mutex
        ReleaseMutex(hMutexHandled);
    }
    return 0;
}


int _tmain(int argc, _TCHAR* argv[]) {
    // Creating mutexes with default valuesю
    // all mutexes are created in the "open" state
    hMutexReady = CreateMutex(NULL, false, NULL);
    hMutexHandled = CreateMutex(NULL, false, NULL);

    // We close the "information generated" mutex, because it hasn't been generated yet
    WaitForSingleObject(hMutexReady, INFINITE);

    // Creating producer and consumer threads
    HANDLE hProducer = CreateThread(NULL, 0, producer, NULL, 0, NULL);
    HANDLE hConsumer = CreateThread(NULL, 0, consumer, NULL, 0, NULL);

    HANDLE hThread[2];
    hThread[0] = hProducer;
    hThread[1] = hConsumer;

    // Waiting for both threads to complete
    WaitForMultipleObjects(2, hThread, true, INFINITE);

    CloseHandle(hProducer);
    CloseHandle(hConsumer);

    return 0;
}

Help me please!

IMPORTANT! This code is for my university, I need to implement exactly this functionality using mutexes from the Windows library.


Solution

  • For starters, you have three threads.

    • main
    • producer
    • consumer.

    The most important rule to remember is that any thread that acquires a mutex via WaitForSingleObject can be the only thread that invokes ReleaseMutex. Any thread that attempts to invoke ReleaseMutex on a mutex not owned by that thread or not owned at all is basically an error and the return value from ReleaseMutex is almost certainly FALSE.

    As you have it, your ReleaseMutex(hMutexHandled) call in producer is failing because that thread wasn't the one that actually called WaitForSingleObject on it.

    Second, consumer is blocked forever on WaitForSingleObject(hMutexReady, INFINITE) because main acquired that mutex before consumer was started.

    If you only have 1 variable to guard between producer and consumer and the intent is that the producer needs to wait for the consumer to finish before generating a new value, then this is easy. You only need one mutex. But to signal readiness between threads, you don't need an additional mutex, you need an event object which is a variation on a classic conditional variable.

    #include <windows.h>
    #include <stdio.h>
    #include <iostream>
    
    BOOL dataPending; // is set to true when new data is produced
    
    HANDLE hMutex;
    HANDLE hEventDataProduced;
    HANDLE hEventDataConsumed;
    
    
    DWORD WINAPI producer(LPVOID param) {
    
        WaitForSingleObject(hMutex, INFINITE);
    
        while (true) {
    
            // wait for consumer thread to accept any pending data
            while (dataPending == TRUE) {
                ReleaseMutex(hMutex);
                WaitForSingleObject(hEventDataConsumed, INFINITE); // wait for consumer to indicate its done
                WaitForSingleObject(hMutex, INFINITE);
            }
    
            // Not shown: Generate the "the data" however that's meant to happen
            // and assign to whatever global or shared variable
    
            // not shown breaking out of the loop when a certain condition is met
    
            dataPending = TRUE;
    
            SetEvent(hEventDataProduced);  // wake up consumer thread
    
            std::cout << "Produced\n";
        }
    
        ReleaseMutex(hMutex);
    }
    
    DWORD WINAPI consumer(LPVOID param) {
    
        WaitForSingleObject(hMutex, INFINITE);
    
        while (true) {
    
            // not shown breaking out of the loop when a certain condition is met
    
            // wait for producer thread to give us something
            while (dataPending == FALSE) {
                ReleaseMutex(hMutex);
                WaitForSingleObject(hEventDataProduced, INFINITE);
            }
    
            // consume the data from the global/shared memory however that's supposed to be done
    
            dataPending = FALSE;
    
            std::cout << "Consumed\n";
            SetEvent(hEventDataConsumed); // notify producer thread it can continue
        }
    
        ReleaseMutex(hMutex);
    }
    
    
    int main(int argc, char* argv[]) {
    
        // 1 mutex for guarding the data and the "dataPending" variable
        hMutex = CreateMutex(NULL, false, NULL);
    
        // two event objects to signal between threads about state
        hEventDataProduced = CreateEvent(NULL, FALSE, FALSE, NULL);
        hEventDataConsumed = CreateEvent(NULL, FALSE, FALSE, NULL);
    
        dataPending = FALSE;
    
        // Creating producer and consumer threads
        HANDLE hProducer = CreateThread(NULL, 0, producer, NULL, 0, NULL);
        HANDLE hConsumer = CreateThread(NULL, 0, consumer, NULL, 0, NULL);
    
        HANDLE hThread[2] = { hProducer, hConsumer };
    
        // Waiting for both threads to complete
        WaitForMultipleObjects(2, hThread, true, INFINITE);
    
        CloseHandle(hProducer);
        CloseHandle(hConsumer);
    
        return 0;
    }
    
    

    Technically, with Win32 events you don't need the dataPending variable since spurious wake-up isn't really an issue with events. But I think it makes the code a bit more readable and easier to understand. Also useful for rooting out bugs.

    Also, you may be able to simplify this code to just use a single event object instead of a pair. I'll leave that as an exercise for you.