threads and forks

    The child process created by the parent calling fork inherits a copy of the entire address space, as well as the state of each mutex, read-write lock, and condition variable. If the parent process contains more than one thread, the child process needs to clear the lock state after the fork returns, if not immediately after calling exec. Because inside the child process, there is only one thread that is a copy of the thread that called fork in the parent process. If threads in the parent process hold locks, the child process will also hold those locks. The problem is that the child process may not contain a copy of the thread holding the lock, so it has no way of knowing which locks it holds and which locks it needs to release.
    Therefore, in a multithreaded process, in order to avoid the problem of inconsistent state, POSIX.1 states that between the return of fork and the invocation of the exec family function by the subprocess, the subprocess can only call functions that are asynchronous and signal safe. This limits what the child process can do before calling exec, but does not involve lock state issues. To clear the lock state, you can call the pthread_atfork function to establish a fork handler (for condition variables, since it may be protected by a global lock, or it may be directly embedded in the data structure of the condition variable, there is currently no portable method. state cleanup on such locks).
#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
                                  /* Return value: if successful, return 0; otherwise, return error number */

    Use this function to install up to 3 functions that clean up locks at a time. The prepare function is called by the parent process before fork creates the child process to obtain all the locks defined by the parent process. The parent function is called in the context of the parent process after fork creates the child process and before returning to unlock all locks acquired by prepare. The child function is called in the context of the child process before fork returns to release all locks acquired by prepare.
    This function can be called multiple times to set up multiple sets of fork handlers, and a null pointer can be passed in the corresponding position for an unneeded handler. The order in which multiple fork handlers are called is not the same. parent and child are called in the order in which they were registered, while prepare is called in the reverse order of registration. This allows multiple modules to register their own fork handlers, while maintaining lock hierarchies. For example, suppose module A calls functions in module B, and each module has its own set of locks. If the lock hierarchy is A before B, then module B must set its fork handler before module A. When the parent process calls fork, the following steps will be performed (assuming the child process executes before the parent process):
    (1) Call the prepare of module A to acquire all the locks of module A.
    (2) Call prepare of module B to acquire all locks of module B.
    (3) Create a child process.
    (4) Call the child handler in module B to release all locks of module B in the child process.
    (5) Call the child handler in module A to release all locks of module A in the child process.
    (6) The fork function returns to the child process.
    (7) Call the parent handler in module B to release all locks of module B in the parent process.
    (8) Call the parent handler in module A to release all locks of module A in the parent process.
    (9) The fork function returns to the parent process.
    The following program describes how to use pthread_atfork and the fork handler.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t	lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

void prepare(void){
	printf("prepare acquiring locks\n");
	pthread_mutex_lock(&lock1);
	pthread_mutex_lock(&lock2);
}

void parent(void){
	printf("parent unlocking locks\n");
	pthread_mutex_unlock(&lock1);
	pthread_mutex_unlock(&lock2);
}

void child(void){
	printf("child unlocking locks\n");
	pthread_mutex_unlock(&lock1);
	pthread_mutex_unlock(&lock2);
}

void *thr_fn(void *arg){
	printf("thread started\n");
	pause();
	printf("thread ended\n");  // can't reach here, for no signal handlers.
	return (void *)0;
}

int main(void){
	if(pthread_atfork(prepare, parent, child) != 0){
		printf("pthread_atfork error\n");
		exit(1);
	}
	pthread_t	tid;
	if(pthread_create(&tid, NULL, thr_fn, NULL) != 0){
		printf("pthread_create error\n");
		exit(1);
	}
	sleep(2);		// Not reliably waiting for thread to run.
	printf("parent process about to call fork()...\n");
	pid_t	pid;
	if((pid = fork()) < 0){
		printf("fork error\n");
		exit(1);
	}
	if(pid == 0)
		printf("child returned from fork\n");
	else
		printf("parent returned from fork\n");
	exit(0);
}

    The results are as follows:
$ ./atforkDemo.out
thread started
parent process about to call fork()...
prepare acquiring locks
parent unlocking locks
parent returned from fork
child unlocking locks
child returned from fork

    This shows that the prepare handler runs after the call to fork, the child executes before the call returns to the child process, and the parent runs before the fork call returns to the parent process.
    While the intent of pthread_atfork is to keep the lock state consistent after a fork, it suffers from the following deficiencies and is therefore only usable in limited circumstances.
   (1) There is no good way to reinitialize the state of more complex synchronization objects (such as condition variables and barriers).
   (2) Some error-checked mutex implementations generate errors when the child fork handler attempts to unlock a mutex locked by the parent process.
   (3) The recursive mutex cannot be cleaned up in the child fork handler because the number of locks cannot be determined.
   (4) If the child process is only allowed to call async-signal-safe functions, it is impossible for the child fork handler to clean up the sync object, because none of the functions used to operate clean-up are async-signal safe. The actual problem is that the synchronization object may be in an intermediate state when a thread calls fork and cannot be cleaned up unless the synchronization object is in a consistent state.
   (5) If the application calls fork in the signal handler (which is legal because fork itself is asynchronous signal safe), the fork handler registered by pthread_atfork can only call the asynchronous signal safe function, otherwise the result will be undefined.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326144378&siteId=291194637