102-多线程与 fork

来源:互联网 发布:安知我意txt百度云 编辑:程序博客网 时间:2024/05/16 07:26

在多线程程序中使用 fork,可能会导致一些意外:

  • 子进程中只剩下一个线程,它是父进程中调用 fork 的线程的副本构成。这意味着在多线程环境中,会导致“线程蒸发”,莫名奇妙的失踪!
  • 因为线程蒸发,它们所持有的锁也可能未释放,这将导致子进程在获取锁时进入死锁。

本文将验证这两个问题,并给出一个可行的解决方案。

1. 线程蒸发

如果在多线程环境中执行 fork,派生的子进程是单线程,子进程中的线程是由父进程中调用 fork 的那个线程的副本构成,而子进程中所有其它的线程会消失。

程序 th_fork 的功能是在父进程中创建一个线程,不断打印自己的 pid 和 ppid。创建完线程后,父进程在主线程中执行 fork,子进程每 2 秒打印一个点。

1.1 代码

// th_fork.c#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <errno.h>#define PERR(err, msg) do { errno = err; perror(msg); exit(-1); } while(0)void* fun(void* arg) {  while(1) {    printf("I'm %d, my father is %d\n", getpid(), getppid());    sleep(1);  }  return NULL;}int main() {  int err;  pid_t pid;  pthread_t tid;  pthread_create(&tid, NULL, fun, NULL);  puts("parent about to fork ...");  pid = fork();  if (pid < 0) PERR(errno, "fork");  else if (pid == 0) {    // child    int status;    err = pthread_join(tid, (void**)&status);    if (err != 0) PERR(err, "pthread_join");    while(1) {      puts(".");      sleep(2);    }       exit(0);  }  pthread_join(tid, NULL);}

1.2 编译和运行

$ gcc th_fork.c -o th_fork -lpthread$ ./th_fork


这里写图片描述
图1 线程蒸发

从图 1 中可以看到,子进程中只有一个线程在打点,子进程的 fun 函数线程已经“蒸发”掉了。

另外,在子进程中调用了 pthread_join 函数并没有报错,看起来就好像是子进程中的 fun 函数线程已经正常结束了。

2. fork 死锁

如果在 fork 的时候,线程未释放持有的锁,将导致死锁。

程序 fork_lock 演示了这种情况。

2.1 代码

// fork_lock.c#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <errno.h>#define PERR(err, msg) do { errno = err; perror(msg); exit(-1); } while(0)int total = 0;pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void* fun(void* arg) {  while(1) {    pthread_mutex_lock(&lock);    total++;    puts("fun:  total++");    // 时间稍稍长一点,在这个时候父进程发起 fork 就导致持锁线程蒸发    sleep(5);    pthread_mutex_unlock(&lock);    sleep(1);  }  return NULL;}int main() {  int err;  pid_t pid;  pthread_t tid;  pthread_create(&tid, NULL, fun, NULL);  // 推迟 1 秒,让线程进入临界区。  sleep(1);  puts("parent about to fork ...");  pid = fork();  if (pid < 0) PERR(errno, "fork");  else if (pid == 0) {    // child    int status;    while(1) {      // 由于 fun 线程蒸发,子进程获取锁就会死锁      puts("child require lock...");      pthread_mutex_lock(&lock);      total++;      puts("child: total++");      sleep(2);      pthread_mutex_unlock(&lock);      sleep(1);    }       exit(0);  }  pthread_join(tid, NULL);}

2.2 编译和运行

$ gcc fork_lock.c -o fork_lock -lpthread$ ./fork_lock


这里写图片描述
图2 fork 死锁

图 2 中,子进程在请求锁后,再也没有回应,进入死锁。

2.3 解决方案

初始的解决方案是在 fork 前先请求所有的锁,然后再 fork,另外我们希望 fork 完成后,再对所有锁进行解锁。看起来像下面这样(假设程序中只用了三个互斥量):

pthread_mutex_lock(&lock1);pthread_mutex_lock(&lock2);pthread_mutex_lock(&lock3);pid = fork();pthread_mutex_unlock(&lock1);pthread_mutex_unlock(&lock2);pthread_mutex_unlock(&lock3);if (pid < 0) {  perror("fork");}else if (pid > 0) {  ...}else if (pid == 0) {  ...}

因此,我们将 pid = fork() 那一行改为下面三行:

pthread_mutex_lock(&lock);pid = fork();pthread_mutex_unlock(&lock);

其它的地方不变,重新编译运行,程序正常。


这里写图片描述
图2 将 fork 放入临界区,程序正常

实际上,linux 为我们提供一更加方便的机制,让我们不用每次 fork 的时候都自己加锁——atfork 函数。

关于 atfork 函数,在下一篇文章讨论,其实它所做的事,和我们的解决方案是差不多的。

3. 总结

  • 理解 fork 导致的线程蒸发
  • 理解 fork 死锁问题何时出现
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 苹果6s听筒声音小怎么办 手机传话器坏了怎么办 微信不能发语音怎么办 台式电脑声卡坏了怎么办 微信说话声音小怎么办 微信不能语音了怎么办 微信不能发语音怎么办? 苹果6话筒声音小怎么办 微信视频杂音大怎么办 微信语音有杂音怎么办 苹果手机音频坏了怎么办 苹果手机送话器坏了怎么办 苹果7听筒声音大怎么办 苹果6听筒声音小怎么办 苹果7电话声音小怎么办 苹果7plus声音小怎么办 苹果7通话音量小怎么办 苹果4s声音小怎么办 苹果4s没有声音怎么办 手机送话筒坏了怎么办 安卓手机声音小怎么办 微信说话没声音怎么办 小米6话筒声音小怎么办 小米4通话声音小怎么办 电容麦有电流声怎么办 苹果6说话声音小怎么办 耳机漏音怎么办小招 win8耳机孔坏了怎么办 魔音耳机开胶了怎么办 苹果6plus声音小怎么办 苹果6铃声不响怎么办 苹果6静音键失灵怎么办 苹果7手机音量小怎么办 苹果4s声音太小怎么办 苹果6声音变小了怎么办 6s扬声器声音小怎么办 苹果手机6听筒声音小怎么办 苹果7听筒声音小怎么办 苹果6音量小了怎么办 苹果手机8声音小怎么办 苹果6plus音量小怎么办