一、普通多线程中执行fork的情况
1.多线程中没有执行fork的情况
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>void*fun(void* arg)
{for(int i=0;i<5;i++){printf("fun线程(%d)在运行\n",getpid());//输出fun线程及其pidsleep(1);//每输出一次睡眠1秒}
}int main()
{pthread_t id;pthread_create(&id,NULL,fun,NULL);for(int i=0;i<5;i++){printf("main线程(%d)在运行\n",getpid());//输出main线程及其pidsleep(1);//每输出一次睡眠1秒}pthread_join(id,NULL);}
运行结果:
通过结果可以看出,两个线程同时执行,并且两个线程的pid是相同的。
2.多线程中在主线程中执行fork的情况
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>void*fun(void* arg)
{for(int i=0;i<5;i++){printf("fun线程(%d)在运行\n",getpid());//输出fun线程及其pidsleep(1);//每输出一次睡眠1秒}
}int main()
{pthread_t id;pthread_create(&id,NULL,fun,NULL);fork();//产生一个子进程for(int i=0;i<5;i++){printf("main线程(%d)在运行\n",getpid());//输出main线程及其pidsleep(1);//每输出一次睡眠1秒}pthread_join(id,NULL);}
运行结果:
根据结果可以看出,在主线程中fork之后,父进程产生的子进程只有一条执行路径,子进程并没有产生新线程。所以产生一个结论,在多线程程序中,无论有多少个线程,fork之后产生的子进程中只有一条执行路径。
3.多线程中在子线程中执行fork的情况
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>void*fun(void* arg)
{fork();//产生一个子进程for(int i=0;i<5;i++){printf("fun线程(%d)在运行\n",getpid());//输出fun线程及其pidsleep(1);//每输出一次睡眠1秒}
}int main()
{pthread_t id;pthread_create(&id,NULL,fun,NULL);for(int i=0;i<5;i++){printf("main线程(%d)在运行\n",getpid());//输出main线程及其pidsleep(1);//每输出一次睡眠1秒}pthread_join(id,NULL);}
运行结果:
从结果可以看出,无论fork在哪个线程被执行,fork最终产生的子进程就在哪个线程中执行。fork是复制进程的函数,从资源的角度来讲,父进程用的资源在fork之后都复制会给子进程。如果父进程运行了多个线程,那么在子进程只执行其中一个线程,这个线程就是fork所在的那个线程。无论是单线程还是多线程,fork之后产生的子进程只执行fork所在的那个线程。
4.总结
多线程程序fork之后产生的子进程只有一条执行路径,就是子进程所在的执行路径。
二、加锁的多线程执行fork的情况
1.创建一个互斥锁,在父进程中加锁,fork之后,子进程的情况
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>
#include<sys/wait.h>pthread_mutex_t mutex;//创建一个互斥锁变量void*fun(void* arg)
{pthread_mutex_lock(&mutex);//加锁sleep(5);pthread_mutex_unlock(&mutex);//5秒后解锁printf("线程fun释放这个锁\n");}int main()
{pthread_mutex_init(&mutex,NULL);//初始化互斥锁pthread_t id;pthread_create(&id,NULL,fun,NULL);sleep(1);//睡眠1秒,等fun线程启动,并且已经加锁pid_t pid=fork();if(pid==0){printf("子进程想要加锁\n");pthread_mutex_lock(&mutex);//子进程加锁printf("子进程加锁成功\n");pthread_mutex_unlock(&mutex);//子进程解锁}else{wait(NULL);//父进程等待子进程结束}printf("main程序结束\n");
}
代码思路:首先创建一个全局的互斥锁,然后在主线程中创建了一个线程fun,在fun中先执行加锁的操作,然后等待5秒,执行解锁的操作,然后线程fun就结束了,在当主线程中创建完新线程fun之后,等待1秒,等待1秒是为了在1秒钟以后在线程fun中已经加锁了,然后执行fork产生一个子进程,在子进程中执行加锁,如果可以加锁成功就会输出加锁成功,父进程在等待子进程结束,如果子进程可以成功执行加锁解锁操作顺利结束,wait就可以返回了,否则wait就会阻塞,等待子进程结束。
运行结果:
根据结果可以看先输出子进程想要加锁,但是没有打印出子进程加锁成功,此时线程fun已经释放锁了,但是子进程依然没有加锁成功,说明子进程在加锁的地方阻塞住了,又由于主线程在wait等待子进程结束,因为子进程没有结束,所以主线程在wait的地方也阻塞住了,导致当前的程序一直没有执行完成。这说明子进程加锁没有成功。而一般加锁不成功的原因是因为已经加锁了,这种情况下才会在加锁的时候发生阻塞。所以,有可能是子进程已经加锁了,所以再次加锁的时候被阻塞住了。
子进程加锁没有成功的详细原因:
父进程加锁之后,执行fork产生子进程,这个锁也会被复制,所以父进程和子进程各自又都锁,而fork的时候锁的状态是加锁状态,所以fork产生的子进程也是处于加锁状态。但是要注意父进程和子进程的锁各自是各自的,是不同的两个锁,相互之间不影响,所以当父进程的线程fun释放锁之后,子进程中锁的状态不会改变,还是加锁状态。所以如果父进程中有互斥锁,那么父进程中锁的状态是什么,在fork之后产生的子进程中的锁的状态就是什么。
2.pthread_atfork()
参数解释:
3个参数都是函数指针
第1个参数:指向的函数是在fork之前执行
第2个参数:指向的函数是在fork之后父进程中执行
第2个参数:指向的函数是在fork之后子进程中执行
返回值为0表示函数执行成功。值得注意的是,无论函数定义在哪里,只有在下一个fork()执行前,该函数才被执行。
在第一个参数指向的函数中加锁,在第二个参数指向的函数中解锁,并释放锁,在第三个参数指向的函数中也释放锁。
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>
#include<sys/wait.h>pthread_mutex_t mutex;//创建一个互斥锁变量void parent_fun(void)//该函数在fork之前执行
{pthread_mutex_lock(&mutex);//加锁
}void child_fun(void)//该函数在fork之后执行
{pthread_mutex_unlock(&mutex);//解锁
}void*fun(void* arg)
{pthread_mutex_lock(&mutex);//加锁sleep(5);pthread_mutex_unlock(&mutex);//5秒后解锁printf("线程fun释放这个锁\n");}int main()
{pthread_mutex_init(&mutex,NULL);//初始化互斥锁//在fork之前执行parent_fun,//fork之后在父进程中执行第二个参数child_fun,在子进程中执行第三个参数child_funpthread_atfork(parent_fun,child_fun,child_fun);pthread_t id;pthread_create(&id,NULL,fun,NULL);sleep(1);//睡眠1秒,等fun线程启动,并且已经加锁pid_t pid=fork();if(pid==0){printf("子进程想要加锁\n");pthread_mutex_lock(&mutex);printf("子进程加锁成功\n");pthread_mutex_unlock(&mutex);}else{wait(NULL);}printf("main程序结束\n");
}
运行结果:
3.总结
父进程中有互斥锁,那么父进程中锁的状态是什么,在fork之后产生的子进程中的锁的状态就是什么。如果在多线程程序中父进程中加了锁,在fork之后,子进程中要使用这个锁,那么就需要用到pthread_atfork(),pthread_atfork()的实现思路就是看没有进程用锁的时候,在进行fork去产生子进程,以确保父子进程中锁的状态是清晰的。