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.
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.