c++windowswinapithread-synchronization

Can the Windows API use two mutexes to achieve thread synchronization?


There is a question in my homework: Use mutex to achieve thread synchronization using the Windows API. I tried using two mutexes to achieve this, but I failed because I found out that a thread cannot use ReleaseMutex() to release a mutex if it has not locked the mutex itself. So, is it possible to achieve thread synchronization using two mutexes?

Here is my code. My code is intended to print Thread A is running and Thread B is running in order. IOW once thread A has printed a line, the next line should be from thread B, and once thread B as printed a line, the next line should be from thread A.

Like this:

Thread A is running
Thread B is running
Thread A is running
Thread B is running
...

But it actually act like this:

Thread A is running
ReleaseMutex failed. Error: 288
Thread A is running
ReleaseMutex failed. Error: 288
Thread A is running
ReleaseMutex failed. Error: 288
...(Print "Thread A is running ReleaseMutex failed. Error: 288" 10 times, and then block)

In the threadA() function, ReleaseMutex(mutexB) failed with ERROR: 288. Additionally, WaitForSingleObject(mutexA, INFINITE) also encounters the same ERROR: 288, but it does not block. Instead, it runs 10 times and prints 'Thread A is running'. However, threadB is blocked forever.

Please help me resolve this issue!!!!

Is it possible to achieve thread synchronization using two mutexes?

Why does ReleaseMutex() fail?

Why does WaitForSingleObject(mutex, INFINITE) not block and instead return a value?

HANDLE mutexA;
HANDLE mutexB;
unsigned __stdcall threadA(void*) {
    for (int i = 0; i < 10; i++) {
        WaitForSingleObject(mutexA, INFINITE);
        cout << "Thread A is running" << endl;
        if (!ReleaseMutex(mutexB)) {
            cout << "ReleaseMutex failed. Error: " << GetLastError() << endl;
        }
    }
    return 0;
}
unsigned __stdcall threadB(void*) {
    for (int i = 0; i < 10; i++) {
        WaitForSingleObject(mutexB, INFINITE);
        cout << "Thread B is running" << endl;
        if (!ReleaseMutex(mutexA)) { 
            cout << "ReleaseMutex failed. Error: " << GetLastError() << endl;
        }
    }
    return 0;
}

int main() {
    unsigned threadID1, threadID2;
    mutexA = CreateMutex(nullptr, FALSE, nullptr);// Initialize the mutex and by default
    mutexB = CreateMutex(nullptr, TRUE, nullptr);// Initialize the mutex and lock it immediately
    auto hThreadA = (HANDLE)_beginthreadex(nullptr, 0, threadA, nullptr, 0, &threadID1);
    auto hThreadB = (HANDLE)_beginthreadex(nullptr, 0, threadB, nullptr, 0, &threadID2);
    WaitForSingleObject(hThreadA, INFINITE);
    WaitForSingleObject(hThreadB, INFINITE);
    CloseHandle(hThreadA);
    CloseHandle(hThreadB);
    CloseHandle(mutexA);
    CloseHandle(mutexB);
    return 0;
}

Solution

  • Only the thread that owns a mutex can release it. What you are attempting to do can be done with a mutex, but not in the way you are trying to do it. Try something more like this instead:

    // alternatively, use std::(j)thread and std::mutex
    // instead of the Win32 API...
    
    HANDLE mutex;
    char whichThread;
    
    unsigned __stdcall threadA(void*) {
        for (int i = 0; i < 10; i++) {
            do {
                WaitForSingleObject(mutex, INFINITE);
                if (whichThread != 'A') {
                    ReleaseMutex(mutex);
                    Sleep(0);
                    continue;
                }
            } while (true);
            cout << "Thread A is running" << endl;
            whichThread = 'B';
            ReleaseMutex(mutex);
        }
        return 0;
    }
    
    unsigned __stdcall threadB(void*) {
        for (int i = 0; i < 10; i++) {
            do {
                WaitForSingleObject(mutex, INFINITE);
                if (whichThread != 'B') {
                    ReleaseMutex(mutex);
                    Sleep(0);
                    continue;
                }
            } while (true);
            cout << "Thread B is running" << endl;
            whichThread = 'A';
            ReleaseMutex(mutex);
        }
        return 0;
    }
    
    int main() {
        mutex = CreateMutex(nullptr, FALSE, nullptr);
        whichThread = 'A';
    
        unsigned threadID1, threadID2;
        auto hThreadA = (HANDLE) _beginthreadex(nullptr, 0, threadA, nullptr, 0, &threadID1);
        auto hThreadB = (HANDLE) _beginthreadex(nullptr, 0, threadB, nullptr, 0, &threadID2);
    
        WaitForSingleObject(hThreadA, INFINITE);
        WaitForSingleObject(hThreadB, INFINITE);
    
        CloseHandle(hThreadA);
        CloseHandle(hThreadB);
        CloseHandle(mutex);
    
        return 0;
    }
    

    That being said, I would suggest using either Condition Variables or Event Objects instead, eg:

    // alternatively, use std::(j)thread, std::mutex, and
    // std::condition_variable instead of the Win32 API ...
    
    CRITICAL_SECTION critSec;
    CONDITION_VARIABLE condVarA, condVarB;
    char whichThread;
    
    unsigned __stdcall threadA(void*) {
        for (int i = 0; i < 10; i++) {
            EnterCriticalSection(&critSec);
            while (whichThread != 'A') {
                SleepConditionVariableCS(&condVarB, &critSec, INFINITE);
            }
            cout << "Thread A is running" << endl;
            whichThread = 'B';
            LeaveCriticalSection(&critSec);
            WakeConditionVariable(&condVarA);
        }
        return 0;
    }
    
    unsigned __stdcall threadB(void*) {
        for (int i = 0; i < 10; i++) {
            EnterCriticalSection(&critSec);
            while (whichThread != 'B') {
                SleepConditionVariableCS(&condVarA, &critSec, INFINITE);
            }
            cout << "Thread B is running" << endl;
            whichThread = 'A';
            LeaveCriticalSection(&critSec);
            WakeConditionVariable(&condVarB);
        }
        return 0;
    }
    
    int main() {
        InitializeCriticalSection(&critSec);
        InitializeConditionVariable(&condVarA);
        InitializeConditionVariable(&condVarB);
        whichThread = 'A';
    
        unsigned threadID1, threadID2;
        auto hThreadA = (HANDLE) _beginthreadex(nullptr, 0, threadA, nullptr, 0, &threadID1);
        auto hThreadB = (HANDLE) _beginthreadex(nullptr, 0, threadB, nullptr, 0, &threadID2);
    
        WaitForSingleObject(hThreadA, INFINITE);
        WaitForSingleObject(hThreadB, INFINITE);
    
        CloseHandle(hThreadA);
        CloseHandle(hThreadB);
    
        DeleteCriticalSection(&critSecA);
        DeleteCriticalSection(&critSecB);
    
        return 0;
    }
    

    Or:

    // alternatively, use std::(j)thread, but there is
    // no standard C++ replacement for Win32 Events, but
    // you can make your own RAII wrapper if needed...
    
    HANDLE hEventA;
    HANDLE hEventB;
    
    unsigned __stdcall threadA(void*) {
        for (int i = 0; i < 10; i++) {
            WaitForSingleObject(hEventB, INFINITE);
            cout << "Thread A is running" << endl;
            ResetEvent(hEventB);
            SetEvent(hEventA);
        }
        return 0;
    }
    
    unsigned __stdcall threadB(void*) {
        for (int i = 0; i < 10; i++) {
            WaitForSingleObject(hEventA, INFINITE);
            cout << "Thread B is running" << endl;
            ResetEvent(hEventA);
            SetEvent(hEventB);
        }
        return 0;
    }
    
    int main() {
        hEventA = CreateEvent(nullptr, TRUE, FALSE, nullptr);
        hEventB = CreateEvent(nullptr, TRUE, TRUE, nullptr);
    
        unsigned threadID1, threadID2;
        auto hThreadA = (HANDLE) _beginthreadex(nullptr, 0, threadA, nullptr, 0, &threadID1);
        auto hThreadB = (HANDLE) _beginthreadex(nullptr, 0, threadB, nullptr, 0, &threadID2);
    
        WaitForSingleObject(hThreadA, INFINITE);
        WaitForSingleObject(hThreadB, INFINITE);
    
        CloseHandle(hThreadA);
        CloseHandle(hThreadB);
        CloseHandle(hEventA);
        CloseHandle(hEventB);
    
        return 0;
    }