简介
根据大碰撞!当Linux多线程遭遇Linux多进程.md文章讲解所作的实验。
测试用例1
#include <stdio.h>#include <unistd.h>#include <pthread.h>void *sub_pthread(void *unused){printf("Id %d:in %s\n", getpid(), __func__);usleep(500);}int main(int argc, char **arhv){int pid;pthread_t ptid;pthread_create(&ptid, NULL, sub_pthread, NULL);if ((pid = fork()) < 0){printf("fork failed\n");}else if (pid == 0){printf("ID %d {childdren}\n", getpid());while(1);}else{printf("ID %d {father}\n", getpid());while(1);}return 0;}
执行结果
root@zhou 12:46:09 ~/P/L/signal-safe # ./signal-safe1ID 31888 {father}ID 31890 {childdren}Id 31888:in sub_pthread
我们可以看到的是子线程函数只执行了一次,还是被父函数执行的。可是子进程为什么没有执行了?
参考下面两个文章。
谨慎使用多线程中的fork
fork(2) - Linux man page
The child process is created with a single thread--the one that called fork().The entire virtual address space of the parent is replicated in the child,including the states of mutexes, condition variables, and other pthreads objects;the use of pthread_atfork(3) may be helpful for dealing with problemsthat this can cause.
进程复制的时候,只会复制当前进程对应的一个线程,只有一个当前的正在执行的线程,其他线程都会被丢弃。
测试用例2
#include <pthread.h>#include <stdio.h>#include <unistd.h>pthread_mutex_t lock;void *sub_pthread(void *unused) {while (1) {pthread_mutex_lock(&lock);printf("sub_pthread has lock\n");usleep(500);pthread_mutex_unlock(&lock);printf("sub_pthread has unlock\n");usleep(500);}pthread_exit(0);}int main(int argc, char **arhv) {int pid;pthread_t ptid;pthread_create(&ptid, NULL, sub_pthread, NULL);usleep(500);if ((pid = fork()) < 0) {printf("fork failed\n");} else if (pid == 0) {printf("ID %d {childdren}\n", getpid());fflush(stdout);while (1) {pthread_mutex_lock(&lock);printf("childdren has lock\n");usleep(500);pthread_mutex_unlock(&lock);printf("childdren has unlock\n");usleep(500);}} else {printf("ID %d {father}\n", getpid());fflush(stdout);while (1) {pthread_mutex_lock(&lock);printf("father has lock\n");usleep(500);pthread_mutex_unlock(&lock);printf("father has unlock\n");usleep(500);}}return 0;}
运行结果
root@zhou 21:36:38 ~/P/L/signal-safe # ./signal-safe2sub_pthread has lockID 1458 {father}father has locksub_pthread has unlockID 1460 {childdren}father has unlocksub_pthread has locksub_pthread has unlockfather has lockfather has unlocksub_pthread has locksub_pthread has unlockfather has lockfather has unlock
我们可以观察到子进程出现了问题,并没有出现childdren has lock这一句信息。原因是子进程并没有得到锁,一直死锁在这里了。
可是为什么了? 为什么子进程得不到锁?
在《Unix环境高级编程 第3版》的12.9章节中是这么描述的
子进程通过继承整个地址空间的副本,还从父进程那儿继承了每个互斥量、读写锁和条件变量的状态。如果父进程包含一个以上的线程,子进程在fork返回以后,如果紧接着不是马上调用exec的话,就需要清理锁状态。在子进程内部,只存在一个线程,它是由父进程中调用fork的线程的副本构成的。如果父进程中的线程占有锁,子进程将同样占有这些锁。问题是子进程并不包含占有锁的线程的副本,所以子进程没有办法知道它占有了哪些锁、需要释放哪些锁。......在多线程的进程中,为了避免不一致状态的问题,POSIX.1声明,在fork返回和子进程调用其中一个exec函数之间,子进程只能调用异步信号安全的函数。这就限制了在调用exec之前子进程能做什么,但不涉及子进程中锁状态的问题。
我们可以参考线程调度为什么比进程调度更少开销?来分析为什么子进程得不到锁。
首先子进程从父进程继承来了锁的状态。但是在继承锁的状态的时候,此时锁是被sub_pthread持有的。等待fork运行子进程时,锁还是被sub_pthread所持有的。因此了我们
