Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't my mutex work properly in a multi-process C application?

I am hacking away at a uni assignment and have come across a problem with my code which is supposed to spawn 2 processes, where the second process waits for the 1st to complete before executing. This is what I have so far:

sem_t mutex;
int producer; int consumer;
sem_init(&mutex, 0, 1);
producer = fork();
consumer = fork();

if (producer == 0) {
    if (VERBOSE) printf("Running producer\n");
    /* down semaphore */
    sem_wait(&mutex);
    /* START CRITICAL REGION */
    get_files(N);
    /* END CRITICAL REGION */
    /* up semaphore */
    sem_post(&mutex);
    if (VERBOSE) printf("Ending producer\n");
    exit(0);
}

if (consumer == 0) {
    if (VERBOSE) printf("Running consumer\n");
    /* down semaphore */
    sem_wait(&mutex);
    /* START CRITICAL REGION */
    /* do stuff */
    /* END CRITICAL REGION */
    /* up semaphore */
    sem_post(&mutex);
    if (VERBOSE) printf("Ending consumer\n");
    exit(0);
}
/* parent waits for both to complete */
wait(NULL);

Now, I know that in the "real-world" this is really stupid. If my 'consumer' does nothing while until my 'producer' is finished, then you might as well not have 2 processes like this, but the assignment is trying to illustrate a race-condition, so that why we've been specifically told to do it this way.

So, my problem is that the consumer process isn't waiting for the producer. I assumed that since the semaphore was taken down in the producer (sem_wait(&mutex);) then it wouldn't be available to the consumer until sem_post(&mutex); is called in the producer.

Additionally, as best as I can tell, the line wait(NULL); isn't waiting for both processes to complete.

Have I critically misunderstood something?

like image 544
Ash Avatar asked Nov 27 '10 13:11

Ash


People also ask

Is it possible to share mutex between multiple processes?

Mutexes can synchronize threads within the same process or in other processes. Mutexes can be used to synchronize threads between processes if the mutexes are allocated in writable memory and shared among the cooperating processes (see mmap(2)), and have been initialized for this task.

What is mutex multithreading?

Mutex is a synchronization primitive that grants exclusive access to the shared resource to only one thread. If a thread acquires a mutex, the second thread that wants to acquire that mutex is suspended until the first thread releases the mutex.

Can mutex be used across threads?

The mutex class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads.

What is the purpose of a mutex?

In computer programming, a mutex (mutual exclusion object) is a program object that is created so that multiple program thread can take turns sharing the same resource, such as access to a file.


3 Answers

You should have error-checking on your semaphore calls. Use perror() to display the error if sem_wait(), sem_init() or sem_post() returns non-zero.

Secondly, you a creating more processes than you think. Your first fork() results in a parent (with producer non-zero) and a child (with producer zero). Both processes then execute the second fork(), so you now have four processes.

Thirdly, the sem_t variable must be shared between the processes, so it must be stored in a shared memory region. The easiest way to achieve this is with mmap():

sem_t *sem = mmap(NULL, sizeof *sem, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

(Execute that prior to the sem_init() and the first fork()).

Forthly, it's not defined which process will run first, so you can't rely on the producer thread calling sem_wait() before the consumer does. Instead, initialise the semaphore to zero with sem_init(), and only call sem_wait() in the consumer - this will block the consumer. The producer executes, and calls sem_post() when it is done, which allows the consumer to proceed.

The sem_init() call should specify pshared as non-zero and value as 0, so it should look like:

if (sem_init(sem, 1, 0) != 0) {
    perror("sem_init");
    exit(1);
}

Fifth, wait(NULL) only waits for a single child process to exit. Call it twice to wait for two child processes.

like image 70
caf Avatar answered Oct 22 '22 05:10

caf


Just because you fork the producer thread first doesn't mean the OS will schedule it to run first - its quite possible that the consumer actually runs and gets the lock first.

Also, you should check the return value of sem_wait - it is posible to return from it without holding the semaphore.

It is also quite possible (as several people have noted in comments) that semaphores may simply not work across forked processes

EDIT - if you pass a non-zero value to argument 2 of sem_init(sem_t *sem, int pshared, unsigned value) when initializing posix semaphores will work across processes

EDIT - See here for a much better explanation than i could give, complete with source code to do nearly exactly what you want

like image 38
tobyodavies Avatar answered Oct 22 '22 03:10

tobyodavies


Have you provided complete code in the question?

If so, you are missing semaphore initialization. You have to call either sem_init or sem_open prior to using the semaphore.

Read here.

EDIT You are specifying pshared = 0 in the sem_init call. This makes the semaphore process-local (i.e. it can be used only to synchronize threads of one process). fork creates a child process, so the semaphore does not do anything useful.

If pshared has value 0, then the semaphore is shared between the threads of a process. If pshared is non-zero, then the semaphore is shared between processes.

(the quote is from the link above)

like image 1
atzz Avatar answered Oct 22 '22 04:10

atzz