I am having an issue with my multithreaded code and hope someone can help me out.
I wish to print on the console all files and folder starting from a folder given as an argument. I use this function for the enumeration:
void enumerate(char* path) {
HANDLE hFind;
WIN32_FIND_DATA data;
char *fullpath = new char[strlen(path) - 1];
strcpy(fullpath, path);
fullpath[strlen(fullpath) - 1] = '\0';
hFind = FindFirstFile(path, &data);
do {
if (hFind != INVALID_HANDLE_VALUE) {
if (strcmp(data.cFileName, ".") != 0 && strcmp(data.cFileName, ".."))
{
EnterCriticalSection(&crit);
queue.push(data.cFileName);
LeaveCriticalSection(&crit);
ReleaseSemaphore(semaphore, 1, NULL);
if (data.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
{
strcat(fullpath, data.cFileName);
strcat(fullpath, "\\*");
enumerate(fullpath);
}
}
}
} while (FindNextFile(hFind, &data));
FindClose(hFind);
return;
}
When I find a file or a folder, I want to add it to a global queue and have my worker threads print it to the console. My worker threads function is:
DWORD WINAPI print_queue(LPVOID param) {
while (1) {
WaitForSingleObject(semaphore, INFINITE);
EnterCriticalSection(&crit);
char *rez = queue.front();
queue.pop();
LeaveCriticalSection(&crit);
if (strcmp(rez, "DONE") == 0)
break;
else
std::cout << rez << std::endl;
}
return 1;
}
In main, I initialize the semaphore and critical section, both variables declared globally:
semaphore = CreateSemaphore(NULL, 0,1, NULL);
InitializeCriticalSection(&crit);
Then create 4 threads:
thread1 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId1);
thread2 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId2);
thread3 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId3);
thread4 = CreateThread(NULL, 0, print_queue, NULL, 0, &tId4);
I then call the enumerate() function and for strings to the queue that will signal my threads to stop when those strings are reached:
for (int p = 0; p<4; p++)
{
EnterCriticalSection(&crit);
queue.push(done);
LeaveCriticalSection(&crit);
ReleaseSemaphore(semaphore, 1, NULL);
}
Those 4 strings are the stop condition for my threads. I then wait for the threads:
HANDLE * threadArray = new HANDLE[4];
threadArray[0] = thread1;
threadArray[1] = thread2;
threadArray[2] = thread3;
threadArray[3] = thread4;
WaitForMultipleObjects(4, threadArray, TRUE, INFINITE);
And close the semaphore and critical section:
CloseHandle(semaphore);
DeleteCriticalSection(&crit);
For some reason, the output is random garbage and I can't figure out why.
This is an example output:
te(L┤(L
┤(L
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠°┐*╧wM3╧weµFC4
╠╠╠╠╠
My logic was to start the semaphore on 0, enter the critical section whenever operations happened on a queue to protect my data, increment the semaphore in the enumerate() function and decrease it in print_queue().
What might be the problem?
enumerate() has MANY problems:
you are not using strcpy() and strcat() correctly, so you are trashing memory. You are not allocating enough memory to hold the result of strcpy(), which copies characters until it reaches a null terminator. You are allocating memory for 2 fewer characters than needed (the last char in the path, and the null terminator). You should be allocating strlen+1 characters instead of strlen-1 characters. And worse, you are using strcat() to concatenate a filename onto the allocated string without first reallocating the string to make room for the filename.
you are leaking the allocated string, as you never call delete[] for it.
the if inside the loop is missing != 0 when checking strcmp("..").
you are pushing pointers into queue to data that is local to enumerate() and gets overwritten on each loop iteration, and goes out of scope when enumerate() exits. Your threads are expecting pointers to data that are stable and do not disappear behind their backs. This is the root of your garbage output. Consider yourself lucky that your code is simply outputting garbage and not just crashing outright.
you are not testing the data.dwFileAttributes field correctly. You need to use the & (bitwise AND) operator instead of the == (equals) operator. Folders and files can have multiple attributes, but you are only interested in checking for one, so you have to test that specific bit by itself and ignore the rest.
You really should be using std::string instead for string management, and let it handle memory allocations for you.
Also, consider using std::filesystem or boost::filesystem to handle the enumeration.
Also, there is no need to push "DONE" strings into the queue after enumerating. When a thread is signaled and goes to extract a string and sees the queue is empty, just exit the thread.
Try something more like this instead:
#include <windows.h>
#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <conditional_variable>
std::queue<std::string> paths;
std::mutex mtx;
std::conditional_variable cv;
bool done = false;
void enumerate(const std::string &path)
{
std::string searchPath = path;
if ((!searchPath.empty()) && (searchPath[searchPath.length()-1] != '\\'))
searchPath += '\\';
WIN32_FIND_DATA data;
HANDLE hFind = FindFirstFileA((searchPath + "*").c_str(), &data);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if ((strcmp(data.cFileName, ".") != 0) && (strcmp(data.cFileName, "..") != 0))
{
string fullpath = searchPath + data.cFileName;
{
std::lock_guard<std::mutex> lock(mtx);
paths.push(fullpath);
cv.notify_one();
}
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
enumerate(fullpath);
}
}
while (FindNextFileA(hFind, &data));
FindClose(hFind);
}
}
void print_queue()
{
std::unique_lock<std::mutex> lock(mtx);
while (true)
{
cv.wait(lock, [](){ return (!paths.empty()) || done; });
if (paths.empty())
return;
std::string rez = paths.front();
paths.pop();
std::cout << rez << std::endl;
}
}
int main()
{
std::thread thread1(print_queue);
std::thread thread2(print_queue);
std::thread thread3(print_queue);
std::thread thread4(print_queue);
enumerate("C:\\");
done = true;
cv.notify_all();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
return 0;
}
You nowhere have written which kind of queue you use, but I guess it's a queue<char*>. This means it stores only pointers to memory which is owned somewhere else.
When you now do queue.push(data.cFileName); you write a pointer to the queue which is not valid after the next iteration, since data changes there. After enumerate exists the data pointers (and thereby queue elements) will even point to undefined memory, which would explain the output.
To fix this store copies of the file names inside the queue, e.g. by using a queue<std::string>
Related
The C API to POSIX threads requires a special flag to be set if you want to share a mutex between processes in shared memory - see sem_init(). I don't really know what the diff is but I'm having trouble trying to use C++ std::condition_variable in shared memory - its seg faulting. I can't see anything mentioning this in the C++ docs, or the constructors. I was wondering how to / if you can use C++ thread mutex in shared memory. Here is my test code for reference. Note squeue is just a simple (POD) static sized circular queue, and irrelevant stuff is omitted:
#include <iostream>
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
#include "squeue.h"
#define SHM_FILENAME "/shimmy-foo"
#define SQUEUE_LENGTH 10
typedef struct {
squeue<int,SQUEUE_LENGTH> queue;
std::mutex mutex;
std::condition_variable_any condvar;
} SHM;
int main() {
int shm_fd = 0;
SHM * shm_ptr = NULL;
squeue<int,SQUEUE_LENGTH> * queue = NULL;
std::mutex * mutex;
std::condition_variable_any * condvar;
// Init SHM. ftruncate() will zero area.
if((shm_fd = shm_open(SHM_FILENAME, O_CREAT|O_RDWR|O_EXCL, S_IREAD|S_IWRITE)) == -1 ) {
fprintf (stderr, "Could not open shm object. %s\n", strerror(errno));
return errno;
}
else {
fprintf (stderr, "Open shm OK. %d\n", shm_fd);
}
ftruncate(shm_fd, sizeof(SHM));
// Connect the shmptr pointer to set to the shared memory area,
// with desired permissions
if((shm_ptr = (SHM*)mmap(0, sizeof(SHM), PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
fprintf (stderr, "Could not map shm. %s\n", strerror(errno));
return errno;
}
else {
fprintf(stderr, "Mapped shm OK. %p\n", shm_ptr);
}
// Create queue and mutex.
queue = new(&shm_ptr->queue) squeue<int,SQUEUE_LENGTH>();
mutex = new(&shm_ptr->mutex) std::mutex();
condvar = new(&shm_ptr->condvar) std::condition_variable_any();
srand(time(NULL));
while(true) {
cout << "Waiting on lock" << endl;
mutex->lock();
if(!queue->full()) {
int value = rand()%100;
queue->push(value);
cout << "Pushed " << value << endl;
} else {
cout << "Que is full!" << endl;
};
condvar->notify_all(); //Seg fault.
mutex->unlock();
sleep(1);
}
}
I use a similar pattern, however, the standard mutex and condition variables are not designed to be shared between processes. The reason for that is that POSIX requires PTHREAD_PROCESS_SHARED attribute set on process shared mutexes and condition variables but the standard C++ primitives do not do that. On Windows it might be more complicated than that.
You can try using boost process shared mutexes and process shared condition variables instead. Or create your own wrappers for POSIX interfaces.
It could also be that squeue corrupts memory beyond its buffer overwriting the mutex and the condition variable that lay above in memory in struct SHM. I would try commenting out the code that pushes into the queue and see if you still get that crash. I tried your code with queue code commented out and it works as expected.
You may also like to use condition_variable instead of condition_variable_any, because the latter one maintains its own mutex but that mutex is not needed if you notify that condition variable while having the associated mutex locked (as you do).
I have a problem with my code:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <windows.h>
#include <string.h>
#include <math.h>
HANDLE event;
HANDLE mutex;
int runner = 0;
DWORD WINAPI thread_fun(LPVOID lpParam) {
int* data = (int*)lpParam;
for (int j = 0; j < 4; j++) { //this loop necessary in order to reproduce the issue
if ((data[2] + 1) == data[0]) { // if it is last thread
while (1) {
WaitForSingleObject(mutex, INFINITE);
if (runner == data[0] - 1) { // if all other thread reach event break
ReleaseMutex(mutex);
break;
}
printf("Run:%d\n", runner);
ReleaseMutex(mutex);
Sleep(10);
}
printf("Check Done:<<%d>>\n", data[2]);
runner = 0;
PulseEvent(event); // let all other threads continue
}
else { // if it is not last thread
WaitForSingleObject(mutex, INFINITE);
runner++;
ReleaseMutex(mutex);
printf("Wait:<<%d>>\n", data[2]);
WaitForSingleObject(event, INFINITE); // wait till all other threads reach this stage
printf("Exit:<<%d>>\n", data[2]);
}
}
return 0;
}
int main()
{
event = CreateEvent(NULL, TRUE, FALSE, NULL);
mutex = CreateMutex(NULL, FALSE, NULL);
SetEvent(event);
int data[3] = {2,8}; //0 amount of threads //1 amount of numbers
HANDLE t[10000];
int ThreadData[1000][3];
for (int i = 0; i < data[0]; i++) {
memcpy(ThreadData[i], data, sizeof(int) * 2); // copy amount of threads and amount of numbers to the threads data
ThreadData[i][2] = i; // creat threads id
LPVOID ThreadsData = (LPVOID)(&ThreadData[i]);
t[i] = CreateThread(0, 0, thread_fun, ThreadsData, 0, NULL);
if (t[i] == NULL)return 0;
}
while (1) {
DWORD res = WaitForMultipleObjects(data[0], t, true, 1000);
if (res != WAIT_TIMEOUT) break;
}
for (int i = 0; i < data[0]; i++)CloseHandle(t[i]); // close all threads
CloseHandle(event); // close event
CloseHandle(mutex); //close mutex
printf("Done");
}
The main idea is to wait until all threads except one reach the event and wait there, meanwhile the last thread must release them from waiting.
But the code doesn't work reliably. 1 in 10 times, it ends correctly, and 9 times just gets stuck in while(1). In different tries, printf in while (printf("Run:%d\n", runner);) prints different numbers of runners (0 and 3).
What can be the problem?
As we found out in the comments section, the problem was that although the event was created in the initial state of being non-signalled
event = CreateEvent(NULL, TRUE, FALSE, NULL);
it was being set to the signalled state immediately afterwards:
SetEvent(event);
Due to this, at least on the first iteration of the loop, when j == 0, the first worker thread wouldn't wait for the second worker thread, which caused a race condition.
Also, the following issues with your code are worth mentioning (although these issues were not the reason for your problem):
According to the Microsoft documentation on PulseEvent, that function should not be used, as it can be unreliable and is mainly provided for backward-compatibility. According to the documentation, you should use condition variables instead.
In your function thread_fun, the last thread is locking and releasing the mutex in a loop. This can be bad, because mutexes are not guaranteed to be fair and it is possible that this will cause other threads to never be able to acquire the mutex. Although this possibility is mitigated by you calling Sleep(10); once in every loop iteration, it is still not the ideal solution. A better solution would be to use a condition variable, so that the thread only checks for changes of the variable runner when another thread actually signals a possible change. Such a solution would also be better for performance reasons.
I am developing an application that reads data from a named pipe on Windows 7 at around 800 Mbps. I have to develop it with several threads since the FIFO at the other side of the pipe overflows if I am not able to read at the given speed. The performance though is really pitifull and I cannot understand why. I already read several things I tried to split the memory to avoid bad memory sharing.
At the beginning I has thinking I could be a problem with contiguous memory possitions, but the memory sections are queued in a list the main thread is not using them any more after queue it. The amount of memory are huge so I don't thing they lay on same pages or so.
This is the threaded function:
void splitMessage(){
char* bufferMSEO;
char* bufferMDO;
std::list<struct msgBufferStr*> localBufferList;
while(1)
{
long bytesProcessed = 0;
{
std::unique_lock<std::mutex> lk(bufferMutex);
while(bufferList.empty())
{
// Wait until the map has data
listReady.wait(lk);
}
//Extract the data from the list and copy to the local list
localBufferList.splice(localBufferList.end(),bufferList);
//Unlock the mutex and notify
// Manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
//listReady.notify_one();
}
for(auto nextBuffer = localBufferList.begin(); nextBuffer != localBufferList.end(); nextBuffer++)
{
//nextBuffer = it->second();
bufferMDO = (*nextBuffer)->MDO;
bufferMSEO = (*nextBuffer)->MSEO;
bytesProcessed += (*nextBuffer)->size;
//Process the data Stream
for(int k=0; k<(*nextBuffer)->size; k++)
{
}
//localBufferList.remove(*nextBuffer);
free(bufferMDO);
free(bufferMSEO);
free(*nextBuffer);
}
localBufferList.clear();
}
}
And here the thread that reads the data and queue them:
DWORD WINAPI InstanceThread(LPVOID lpvParam)
// This routine is a thread processing function to read from and reply to a client
// via the open pipe connection passed from the main loop. Note this allows
// the main loop to continue executing, potentially creating more threads of
// of this procedure to run concurrently, depending on the number of incoming
// client connections.
{
HANDLE hHeap = GetProcessHeap();
TCHAR* pchRequest = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE*sizeof(TCHAR));
DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
BOOL fSuccess = FALSE;
HANDLE hPipe = NULL;
double totalRxData = 0;
char* bufferPnt;
char* bufferMDO;
char* bufferMSEO;
char* destPnt;
// Do some extra error checking since the app will keep running even if this
// thread fails.
if (lpvParam == NULL)
{
printf( "\nERROR - Pipe Server Failure:\n");
printf( " InstanceThread got an unexpected NULL value in lpvParam.\n");
printf( " InstanceThread exitting.\n");
if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest);
return (DWORD)-1;
}
if (pchRequest == NULL)
{
printf( "\nERROR - Pipe Server Failure:\n");
printf( " InstanceThread got an unexpected NULL heap allocation.\n");
printf( " InstanceThread exitting.\n");
return (DWORD)-1;
}
// Print verbose messages. In production code, this should be for debugging only.
printf("InstanceThread created, receiving and processing messages.\n");
// The thread's parameter is a handle to a pipe object instance.
hPipe = (HANDLE) lpvParam;
try
{
msgSplitter = std::thread(&splitMessage);
//msgSplitter.detach();
}
catch(...)
{
_tprintf(TEXT("CreateThread failed, GLE=%d.\n"), GetLastError());
return -1;
}
while (1)
{
struct msgBufferStr *newBuffer = (struct msgBufferStr* )malloc(sizeof(struct msgBufferStr));
// Read client requests from the pipe. This simplistic code only allows messages
// up to BUFSIZE characters in length.
fSuccess = ReadFile(
hPipe, // handle to pipe
pchRequest, // buffer to receive data
BUFSIZE*sizeof(TCHAR), // size of buffer
&cbBytesRead, // number of bytes read
NULL); // not overlapped I/O
if (!fSuccess || cbBytesRead == 0)
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
_tprintf(TEXT("InstanceThread: client disconnected.\n"), GetLastError());
break;
}
else if (GetLastError() == ERROR_MORE_DATA)
{
}
else
{
_tprintf(TEXT("InstanceThread ReadFile failed, GLE=%d.\n"), GetLastError());
}
}
//timeStart = omp_get_wtime();
bufferPnt = (char*)pchRequest;
totalRxData += ((double)cbBytesRead)/1000000;
bufferMDO = (char*) malloc(cbBytesRead);
bufferMSEO = (char*) malloc(cbBytesRead/3);
destPnt = bufferMDO;
//#pragma omp parallel for
for(int i = 0; i < cbBytesRead/12; i++)
{
msgCounter++;
if(*(bufferPnt + (i * 12)) == 0) continue;
if(*(bufferPnt + (i * 12)) == 8)
{
errorCounter++;
continue;
}
//Use 64 bits variables in order to make less operations
unsigned long long *sourceAddrLong = (unsigned long long*) (bufferPnt + (i * 12));
unsigned long long *destPntLong = (unsigned long long*) (destPnt + (i * 8));
//Copy the data bytes from source to destination
*destPntLong = *sourceAddrLong;
//Copy and prepare the MSEO lines for the data processing
bufferMSEO[i*4]=(bufferPnt[(i * 12) + 8] & 0x03);
bufferMSEO[i*4 + 1]=(bufferPnt[(i * 12) + 8] & 0x0C) >> 2;
bufferMSEO[i*4 + 2]=(bufferPnt[(i * 12) + 8] & 0x30) >> 4;
bufferMSEO[i*4 + 3]=(bufferPnt[(i * 12) + 8] & 0xC0) >> 6;
}
newBuffer->size = cbBytesRead/3;
newBuffer->MDO = bufferMDO;
newBuffer->MSEO = bufferMSEO;
{
//lock the mutex
std::lock_guard<std::mutex> lk(bufferMutex);
//add data to the list
bufferList.push_back(newBuffer);
} // bufferMutex is automatically released when lk goes out of scope
//Notify
listReady.notify_one();
}
// Flush the pipe to allow the client to read the pipe's contents
// before disconnecting. Then disconnect the pipe, and close the
// handle to this pipe instance.
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
HeapFree(hHeap, 0, pchRequest);
//Show memory leak isues
_CrtDumpMemoryLeaks();
//TODO: Join thread
printf("InstanceThread exitting.\n");
return 1;
}
The think that really blows my mind is that I a let it like this the splitMessage thread takes minutes to read the data even though the first thread finished reading the data long ago. I mean the read thread reads like 1,5Gb or information in seconds and waits for more data from the pipe. This data are processed by the split thread (the only one really "doing" something in almost one minute or more). The CPU is moreover only to less than 20% percent used. (It is a i7 labtop with 16 Gb RAM and 8 cores!)
On the other hand, if I just comment the for loop in the process thread:
for(int k=0; k<(*nextBuffer)->size; k++)
Then the data are read slowly and the FIFO on the other side of the pipe overflows. With 8 processors and at more than 2 GHz should be fast enought to go throw the buffers without many problems, isn't it? I think it has to be a memory access issue or that the scheduler is sending the thread somehow to sleep but I cannot figure out why!!. Other possibility is that the iteration throw the linked list with the iterator is not optimal.
Any help would be geat because I am trying to understand it since a couple of days, I made several changes in the code and tried to simplified at the maximum and I am getting crazy :).
best regards,
Manuel
#include <pthread.h>
#include <time.h>
#include "errors.h"
typedef struct alarm_tag {
struct alarm_tag *link;
int seconds;
time_t time; /* seconds from EPOCH */
char message[64];
} alarm_t;
pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER;
alarm_t *alarm_list = NULL;
void *alarm_thread (void *arg)
{
alarm_t *alarm;
int sleep_time;
time_t now;
int status;
while (1) {
status = pthread_mutex_lock (&alarm_mutex);
if (status != 0)
err_abort (status, "Lock mutex");
alarm = alarm_list;
/*
* If the alarm list is empty, wait for one second. This
* allows the main thread to run, and read another
* command. If the list is not empty, remove the first
* item. Compute the number of seconds to wait -- if the
* result is less than 0 (the time has passed), then set
* the sleep_time to 0.
*/
if (alarm == NULL)
sleep_time = 1;
else {
alarm_list = alarm->link;
now = time (NULL);
if (alarm->time <= now)
sleep_time = 0;
else
sleep_time = alarm->time - now;
#ifdef DEBUG
printf ("[waiting: %d(%d)\"%s\"]\n", alarm->time,
sleep_time, alarm->message);
#endif
}
/*
* Unlock the mutex before waiting, so that the main
* thread can lock it to insert a new alarm request. If
* the sleep_time is 0, then call sched_yield, giving
* the main thread a chance to run if it has been
* readied by user input, without delaying the message
* if there's no input.
*/
status = pthread_mutex_unlock (&alarm_mutex);
if (status != 0)
err_abort (status, "Unlock mutex");
if (sleep_time > 0)
sleep (sleep_time);
else
sched_yield ();
/*
* If a timer expired, print the message and free the
* structure.
*/
if (alarm != NULL) {
printf ("(%d) %s\n", alarm->seconds, alarm->message);
free (alarm);
}
}
}
int main (int argc, char *argv[])
{
int status;
char line[128];
alarm_t *alarm, **last, *next;
pthread_t thread;
status = pthread_create (
&thread, NULL, alarm_thread, NULL);
if (status != 0)
err_abort (status, "Create alarm thread");
while (1) {
printf ("alarm> ");
if (fgets (line, sizeof (line), stdin) == NULL) exit (0);
if (strlen (line) <= 1) continue;
alarm = (alarm_t*)malloc (sizeof (alarm_t));
if (alarm == NULL)
errno_abort ("Allocate alarm");
/*
* Parse input line into seconds (%d) and a message
* (%64[^\n]), consisting of up to 64 characters
* separated from the seconds by whitespace.
*/
if (sscanf (line, "%d %64[^\n]",
&alarm->seconds, alarm->message) < 2) {
fprintf (stderr, "Bad command\n");
free (alarm);
} else {
status = pthread_mutex_lock (&alarm_mutex);
if (status != 0)
err_abort (status, "Lock mutex");
alarm->time = time (NULL) + alarm->seconds;
/*
* Insert the new alarm into the list of alarms,
* sorted by expiration time.
*/
last = &alarm_list;
next = *last;
while (next != NULL) {
if (next->time >= alarm->time) {
alarm->link = next;
*last = alarm;
break;
}
last = &next->link;
next = next->link;
}
/*
* If we reached the end of the list, insert the new
* alarm there. ("next" is NULL, and "last" points
* to the link field of the last item, or to the
* list header).
*/
if (next == NULL) {
*last = alarm;
alarm->link = NULL;
}
#ifdef DEBUG
printf ("[list: ");
for (next = alarm_list; next != NULL; next = next->link)
printf ("%d(%d)[\"%s\"] ", next->time,
next->time - time (NULL), next->message);
printf ("]\n");
#endif
status = pthread_mutex_unlock (&alarm_mutex);
if (status != 0)
err_abort (status, "Unlock mutex");
}
}
}
Hi this is my code, can anyone tell me because the mutex is not declared in the struct. So when the mutex locks and unlocks, what data is actually being changed can someone enlighten me?
where is this set of data that is being protected by the mutex?
The mutex object is alarm_mutex. The data "protected" by it doesn't have to be explicitely mentioned in the code; as in, there doesn't need to be a semantic connection. A mutex is a low-level threading primitive and as such the user needs to build his own logic around that. In your case, that one place in memory is used to block other parts of your code, those accessing actual data, from interfering.
Think about it this way: std::atomic<int> x; expresses the atomicity of operations on it. int x; mutex m; requires every piece of the code accessing x to properly look at m to ensure the correctness of the program. This low-level acess is what we're looking at in your example.
pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER; creates a shared mutex object, used for locking/unlocking.
pthread_mutex_lock locks the mutex as soon as it is available. It becomes unavailable for all other threads after this line is executed.
pthread_mutex_unlock unlocks the mutex, making it available again for other threads (unlocks the pthread_mutex_lock of another thread)
The mutex doesn't know what it is protecting. It is the programmer's job to know that and only change the data that it is protecting while the mutex is locked.
In this specific case it seems that the alarm list is the data being locked.
The C API to POSIX threads requires a special flag to be set if you want to share a mutex between processes in shared memory - see sem_init(). I don't really know what the diff is but I'm having trouble trying to use C++ std::condition_variable in shared memory - its seg faulting. I can't see anything mentioning this in the C++ docs, or the constructors. I was wondering how to / if you can use C++ thread mutex in shared memory. Here is my test code for reference. Note squeue is just a simple (POD) static sized circular queue, and irrelevant stuff is omitted:
#include <iostream>
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
#include "squeue.h"
#define SHM_FILENAME "/shimmy-foo"
#define SQUEUE_LENGTH 10
typedef struct {
squeue<int,SQUEUE_LENGTH> queue;
std::mutex mutex;
std::condition_variable_any condvar;
} SHM;
int main() {
int shm_fd = 0;
SHM * shm_ptr = NULL;
squeue<int,SQUEUE_LENGTH> * queue = NULL;
std::mutex * mutex;
std::condition_variable_any * condvar;
// Init SHM. ftruncate() will zero area.
if((shm_fd = shm_open(SHM_FILENAME, O_CREAT|O_RDWR|O_EXCL, S_IREAD|S_IWRITE)) == -1 ) {
fprintf (stderr, "Could not open shm object. %s\n", strerror(errno));
return errno;
}
else {
fprintf (stderr, "Open shm OK. %d\n", shm_fd);
}
ftruncate(shm_fd, sizeof(SHM));
// Connect the shmptr pointer to set to the shared memory area,
// with desired permissions
if((shm_ptr = (SHM*)mmap(0, sizeof(SHM), PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
fprintf (stderr, "Could not map shm. %s\n", strerror(errno));
return errno;
}
else {
fprintf(stderr, "Mapped shm OK. %p\n", shm_ptr);
}
// Create queue and mutex.
queue = new(&shm_ptr->queue) squeue<int,SQUEUE_LENGTH>();
mutex = new(&shm_ptr->mutex) std::mutex();
condvar = new(&shm_ptr->condvar) std::condition_variable_any();
srand(time(NULL));
while(true) {
cout << "Waiting on lock" << endl;
mutex->lock();
if(!queue->full()) {
int value = rand()%100;
queue->push(value);
cout << "Pushed " << value << endl;
} else {
cout << "Que is full!" << endl;
};
condvar->notify_all(); //Seg fault.
mutex->unlock();
sleep(1);
}
}
I use a similar pattern, however, the standard mutex and condition variables are not designed to be shared between processes. The reason for that is that POSIX requires PTHREAD_PROCESS_SHARED attribute set on process shared mutexes and condition variables but the standard C++ primitives do not do that. On Windows it might be more complicated than that.
You can try using boost process shared mutexes and process shared condition variables instead. Or create your own wrappers for POSIX interfaces.
It could also be that squeue corrupts memory beyond its buffer overwriting the mutex and the condition variable that lay above in memory in struct SHM. I would try commenting out the code that pushes into the queue and see if you still get that crash. I tried your code with queue code commented out and it works as expected.
You may also like to use condition_variable instead of condition_variable_any, because the latter one maintains its own mutex but that mutex is not needed if you notify that condition variable while having the associated mutex locked (as you do).