目录
一、多线程+fork()
问题一:多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?
1.1.1 不使用fork前,让线程函数和主程序打印其进程号
结果:
结论:
1.1.2 在主程序中加入fork
结果:
结论:
1.1.3 线程函数加入fork()
结果:
结论:
综上所述:多线程程序fork后,子进程只启用一条执行路径,就是fork所在的执行路径。
问题二: 父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁
代码
结果
分析
解决
结果
一、多线程+fork()
问题一:多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?
1.1.1 不使用fork前,让线程函数和主程序打印其进程号
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *fun(void *arg)
{
for (int i = 0; i < 5; i++)
{
printf("fun pid=%d\n", getpid());
sleep(1);
}
}
int main()
{
pthread_t id;
pthread_create(&id, NULL, fun, NULL);
for (int i = 0; i < 5; i++)
{
printf("main pid=%d\n", getpid());
sleep(1);
}
pthread_join(id,NULL);
exit(0);
}
结果:
结论:
不难发现,线程函数和主函数的进程号是一样的
1.1.2 在主程序中加入fork
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *fun(void *arg)
{
for (int i = 0; i < 5; i++)
{
printf("fun pid=%d\n", getpid());
sleep(1);
}
}
int main()
{
pthread_t id;
pthread_create(&id, NULL, fun, NULL);
fork();
for (int i = 0; i < 5; i++)
{
printf("main pid=%d\n", getpid());
sleep(1);
}
pthread_join(id,NULL);
exit(0);
}
结果:
结论:
不难发现父进程中打印主线程和线程函数id=3519,而子进程执行了主线程id=3521,子进程只有一条执行路径。
1.1.3 线程函数加入fork()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *fun(void *arg)
{
fork();
for (int i = 0; i < 5; i++)
{
printf("fun pid=%d\n", getpid());
sleep(1);
}
}
int main()
{
pthread_t id;
pthread_create(&id, NULL, fun, NULL);
for (int i = 0; i < 5; i++)
{
printf("main pid=%d\n", getpid());
sleep(1);
}
pthread_join(id,NULL);
exit(0);
}
结果:
结论:
不难发现父进程中打印主线程和线程函数id=3551,而子进程执行了线程函数id=3553,子进程只有一条执行路径。
综上所述:多线程程序fork后,子进程只启用一条执行路径,就是fork所在的执行路径。
问题二: 父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁
多线程中fork以后产生子进程,子进程共享父进程的内容,但是会不会共享锁或者信号量呢,下面我们举个栗子。
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include<sys/wait.h>
pthread_mutex_t mutex;
void*fun(void*arg)
{
pthread_mutex_lock(&mutex);
printf("fun lock\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("fun unlock\n");
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
sleep(1);
pid_t pid =fork();
if(pid==-1)
{
exit(0);
}
if(pid==0)
{
printf("child 准备 lock\n");
pthread_mutex_lock(&mutex);
printf("child枷锁成功\n");
pthread_mutex_unlock(&mutex);
exit(0);
}
wait(NULL);
printf("main over\n");
exit(0);
}
结果
分析
代码从主程序开始执行,执行到线程函数时,创建线程,进入fun()后,加锁,打印“fun lock”,随后睡眠5秒,我们知道多线程是有并发这个特性,这个时候就会继续主函数,进行fork,这个时候我们发现打印了"child 准备lock",注意此时我们线程函数中的锁还没有解,就有了一个新的锁,说明父进程和子进程的锁不是共用一个锁,此后5秒睡眠时间结束,这时继续执行多线程函数,解锁打印“fun unlock”,但是我们发现一件事:此函数阻塞了。
接下来就是这个问题的核心之所在。
fork()会将父进程的内容给子进程复制一份,同时也会把锁的状态给子进程,如在fork前锁还没有上,那么复制给子进程的锁就是没有上的。所以这里我们在fork前父进程就已经上了锁,传递给子进程后,子进程刚开始的锁就是上锁状态,所以就不会执行上锁状态,因为没有解锁。
解决
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
该函数通过3个不同阶段的回调函数来处理互斥锁状态。参数如下:
prepare:将在fork调用创建出子进程之前被执行,它可以给父进程中的互斥锁对象明明确确上锁。这个函数是在父进程的上下文中执行的,正常使用时,我们应该在此回调函数调用 pthread_mutex_lock 来给互斥锁明明确确加锁,这个时候如果父进程中的某个线程已经调用pthread_mutex_lock给互斥锁加上了锁,则在此回调中调用 pthread_mutex_lock 将迫使父进程中调用fork的线程处于阻塞状态,直到prepare能给互斥锁对象加锁为止。
parent: 是在fork调用创建出子进程之后,而fork返回之前执行,在父进程上下文中被执行。它的作用是释放所有在prepare函数中被明明确确锁住的互斥锁。
child: 是在fork返回之前,在子进程上下文中被执行。和parent处理函数一样,child函数也是用于释放所有在prepare函数中被明明确确锁住的互斥锁。
函数成功返回0, 错误返回错误码。
pthread_mutex_t mutex;
void fork_lock(void)
{
pthread_mutex_lock(&mutex);
}
void fork_unlock(void)
{
pthread_mutex_unlock(&mutex);
}
void * fun(void* arg)
{
pthread_mutex_lock(&mutex);
printf("fun lock\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("fun unlock\n");
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t id;
pthread_atfork(fork_lock,fork_unlock,fork_unlock);
pthread_create(&id,NULL,fun,NULL);
sleep(1);
pid_t pid = fork();
if ( pid == -1 )
{
exit(1);
}
if ( pid == 0 )
{
printf("child 准备lock\n");
pthread_mutex_lock(&mutex);//阻塞
printf("child加锁成功\n");
pthread_mutex_unlock(&mutex);
exit(0);
}
wait(NULL);
printf("main exit\n");
exit(0);
}
结果
到这里线程的同步就更新这么多啦,明天更新生产者消费者模型。