简介

根据大碰撞!当Linux多线程遭遇Linux多进程.md文章讲解所作的实验。

测试用例1

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <pthread.h>
  4. void *sub_pthread(void *unused)
  5. {
  6. printf("Id %d:in %s\n", getpid(), __func__);
  7. usleep(500);
  8. }
  9. int main(int argc, char **arhv)
  10. {
  11. int pid;
  12. pthread_t ptid;
  13. pthread_create(&ptid, NULL, sub_pthread, NULL);
  14. if ((pid = fork()) < 0)
  15. {
  16. printf("fork failed\n");
  17. }
  18. else if (pid == 0)
  19. {
  20. printf("ID %d {childdren}\n", getpid());
  21. while(1);
  22. }
  23. else
  24. {
  25. printf("ID %d {father}\n", getpid());
  26. while(1);
  27. }
  28. return 0;
  29. }

执行结果

  1. root@zhou 12:46:09 ~/P/L/signal-safe # ./signal-safe1
  2. ID 31888 {father}
  3. ID 31890 {childdren}
  4. Id 31888:in sub_pthread

我们可以看到的是子线程函数只执行了一次,还是被父函数执行的。可是子进程为什么没有执行了?
参考下面两个文章。
谨慎使用多线程中的fork
fork(2) - Linux man page

  1. The child process is created with a single thread--the one that called fork().
  2. The entire virtual address space of the parent is replicated in the child,
  3. including the states of mutexes, condition variables, and other pthreads objects;
  4. the use of pthread_atfork(3) may be helpful for dealing with problems
  5. that this can cause.

进程复制的时候,只会复制当前进程对应的一个线程,只有一个当前的正在执行的线程,其他线程都会被丢弃。

测试用例2

  1. #include <pthread.h>
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. pthread_mutex_t lock;
  5. void *sub_pthread(void *unused) {
  6. while (1) {
  7. pthread_mutex_lock(&lock);
  8. printf("sub_pthread has lock\n");
  9. usleep(500);
  10. pthread_mutex_unlock(&lock);
  11. printf("sub_pthread has unlock\n");
  12. usleep(500);
  13. }
  14. pthread_exit(0);
  15. }
  16. int main(int argc, char **arhv) {
  17. int pid;
  18. pthread_t ptid;
  19. pthread_create(&ptid, NULL, sub_pthread, NULL);
  20. usleep(500);
  21. if ((pid = fork()) < 0) {
  22. printf("fork failed\n");
  23. } else if (pid == 0) {
  24. printf("ID %d {childdren}\n", getpid());
  25. fflush(stdout);
  26. while (1) {
  27. pthread_mutex_lock(&lock);
  28. printf("childdren has lock\n");
  29. usleep(500);
  30. pthread_mutex_unlock(&lock);
  31. printf("childdren has unlock\n");
  32. usleep(500);
  33. }
  34. } else {
  35. printf("ID %d {father}\n", getpid());
  36. fflush(stdout);
  37. while (1) {
  38. pthread_mutex_lock(&lock);
  39. printf("father has lock\n");
  40. usleep(500);
  41. pthread_mutex_unlock(&lock);
  42. printf("father has unlock\n");
  43. usleep(500);
  44. }
  45. }
  46. return 0;
  47. }

运行结果

  1. root@zhou 21:36:38 ~/P/L/signal-safe # ./signal-safe2
  2. sub_pthread has lock
  3. ID 1458 {father}
  4. father has lock
  5. sub_pthread has unlock
  6. ID 1460 {childdren}
  7. father has unlock
  8. sub_pthread has lock
  9. sub_pthread has unlock
  10. father has lock
  11. father has unlock
  12. sub_pthread has lock
  13. sub_pthread has unlock
  14. father has lock
  15. father has unlock

我们可以观察到子进程出现了问题,并没有出现childdren has lock这一句信息。原因是子进程并没有得到锁,一直死锁在这里了。
可是为什么了? 为什么子进程得不到锁?
在《Unix环境高级编程 第3版》的12.9章节中是这么描述的

  1. 子进程通过继承整个地址空间的副本,还从父进程那儿继承了每个互斥量、读写锁和条件变量的状态。
  2. 如果父进程包含一个以上的线程,子进程在fork返回以后,
  3. 如果紧接着不是马上调用exec的话,就需要清理锁状态。
  4. 在子进程内部,只存在一个线程,它是由父进程中调用fork的线程的副本构成的。
  5. 如果父进程中的线程占有锁,子进程将同样占有这些锁。
  6. 问题是子进程并不包含占有锁的线程的副本,
  7. 所以子进程没有办法知道它占有了哪些锁、需要释放哪些锁。
  8. ......
  9. 在多线程的进程中,为了避免不一致状态的问题,
  10. POSIX.1声明,在fork返回和子进程调用其中一个exec函数之间,
  11. 子进程只能调用异步信号安全的函数。
  12. 这就限制了在调用exec之前子进程能做什么,但不涉及子进程中锁状态的问题。

我们可以参考线程调度为什么比进程调度更少开销?来分析为什么子进程得不到锁。
首先子进程从父进程继承来了锁的状态。但是在继承锁的状态的时候,此时锁是被sub_pthread持有的。等待fork运行子进程时,锁还是被sub_pthread所持有的。因此了我们

测试用例3