Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forking while holding a lock

I have the following program:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <iostream>


int main() {
   pthread_mutex_t lock_;
   pthread_mutexattr_t ma;
   pthread_mutexattr_init( &ma );
   pthread_mutexattr_setpshared( &ma, PTHREAD_PROCESS_SHARED );
   pthread_mutexattr_settype( &ma, PTHREAD_MUTEX_ERRORCHECK );
   pthread_mutex_init( &lock_, &ma );

   pthread_mutex_lock( &lock_ );

   if(fork()==0) {
      std::cout << "child" << std::endl;
      pthread_mutex_lock( &lock_ );
      std::cout << "finish" << std::endl;
   } else {
      std::cout << "parent" << std::endl;
      sleep(1);
      pthread_mutex_lock( &lock_ );
      std::cout << "parent done" << std::endl;
   }

}

The behaviour I see eis that the parent can re-lock the mutex, but not the child. I would have expected the fork() to fork all of the context of the current thread, so the child would end up with a lock it had locked (IE, i don't want to share the lock - both processes having their own lock is what I want). Why does this not work/how do I accomplish this?

like image 323
Nathaniel Flath Avatar asked Aug 25 '17 20:08

Nathaniel Flath


1 Answers

It doesn't work simply because it is explicitly documented as not working, in a somewhat confusing way. fork() and multi-threaded processes do not play well together.

Although fork() manual page initially starts off by claiming that it "creates a new process by duplicating the calling process", this is a little white lie. If fork() truly did that and duplicated the whole process, it must faithfully duplicate all of the process's execution threads. Because that's what your process is all about: all the execution threads that comprise the overall process.

But that's not what happens.

If you keep reading the fork(2) manual page, you'll get to this part:

The child process is created with a single thread—the one that called fork().

This would be your honking clue number #1 that fork() does not really duplicate the entire process. It duplicates just one of its execution threads.

The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects;

Now, stop for a moment and think what this means: only one execution thread gets forked, but all of that is meticulously duplicated in the child process. The conclusions we must draw from this are rather ugly.

If you think about it: the child process continues with one, lone, execution thread whose mutexes were originally locked by some other thread. But that other thread does not exist in the child process. It wasn't forked. Only one thread was forked.

This is actually clarified in the pthread_atfork(3) manual page:

For example, at the time of the call to fork(2), other threads may have locked mutexes that are visible in the user-space memory duplicated in the child. Such mutexes would never be unlocked, since the threads that placed the locks are not duplicated in the child.

So the bottom line, in your sample code the child process ends up holding a bag containing a bunch of mutexes that are no longer in any kind of a valid state, in the child process.

like image 190
Sam Varshavchik Avatar answered Nov 01 '22 07:11

Sam Varshavchik