• Linux Day16 多线程的一些常见问题


    目录

    一、多线程+fork()

    问题一:多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?

    1.1.1 不使用fork前,让线程函数和主程序打印其进程号

    结果:

    结论:

    1.1.2 在主程序中加入fork

    结果:

    结论:

    1.1.3 线程函数加入fork()

     结果:

    结论:

    综上所述:多线程程序fork后,子进程只启用一条执行路径,就是fork所在的执行路径。

     问题二: 父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁

    代码

    结果

    分析

    解决

    结果


    一、多线程+fork()

    问题一:多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?

    1.1.1 不使用fork前,让线程函数和主程序打印其进程号

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void *fun(void *arg)
    7. {
    8. for (int i = 0; i < 5; i++)
    9. {
    10. printf("fun pid=%d\n", getpid());
    11. sleep(1);
    12. }
    13. }
    14. int main()
    15. {
    16. pthread_t id;
    17. pthread_create(&id, NULL, fun, NULL);
    18. for (int i = 0; i < 5; i++)
    19. {
    20. printf("main pid=%d\n", getpid());
    21. sleep(1);
    22. }
    23. pthread_join(id,NULL);
    24. exit(0);
    25. }

    结果:

    结论:

    不难发现,线程函数和主函数的进程号是一样的

    1.1.2 在主程序中加入fork

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void *fun(void *arg)
    7. {
    8. for (int i = 0; i < 5; i++)
    9. {
    10. printf("fun pid=%d\n", getpid());
    11. sleep(1);
    12. }
    13. }
    14. int main()
    15. {
    16. pthread_t id;
    17. pthread_create(&id, NULL, fun, NULL);
    18. fork();
    19. for (int i = 0; i < 5; i++)
    20. {
    21. printf("main pid=%d\n", getpid());
    22. sleep(1);
    23. }
    24. pthread_join(id,NULL);
    25. exit(0);
    26. }

    结果:

    结论:

    不难发现父进程中打印主线程和线程函数id=3519,而子进程执行了主线程id=3521,子进程只有一条执行路径。

    1.1.3 线程函数加入fork()

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void *fun(void *arg)
    7. {
    8. fork();
    9. for (int i = 0; i < 5; i++)
    10. {
    11. printf("fun pid=%d\n", getpid());
    12. sleep(1);
    13. }
    14. }
    15. int main()
    16. {
    17. pthread_t id;
    18. pthread_create(&id, NULL, fun, NULL);
    19. for (int i = 0; i < 5; i++)
    20. {
    21. printf("main pid=%d\n", getpid());
    22. sleep(1);
    23. }
    24. pthread_join(id,NULL);
    25. exit(0);
    26. }

     结果:

    结论:

    不难发现父进程中打印主线程和线程函数id=3551,而子进程执行了线程函数id=3553,子进程只有一条执行路径。

    综上所述:多线程程序fork后,子进程只启用一条执行路径,就是fork所在的执行路径。

     问题二: 父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁

    多线程中fork以后产生子进程,子进程共享父进程的内容,但是会不会共享锁或者信号量呢,下面我们举个栗子。

    代码

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. pthread_mutex_t mutex;
    8. void*fun(void*arg)
    9. {
    10. pthread_mutex_lock(&mutex);
    11. printf("fun lock\n");
    12. sleep(5);
    13. pthread_mutex_unlock(&mutex);
    14. printf("fun unlock\n");
    15. }
    16. int main()
    17. {
    18. pthread_mutex_init(&mutex,NULL);
    19. pthread_t id;
    20. pthread_create(&id,NULL,fun,NULL);
    21. sleep(1);
    22. pid_t pid =fork();
    23. if(pid==-1)
    24. {
    25. exit(0);
    26. }
    27. if(pid==0)
    28. {
    29. printf("child 准备 lock\n");
    30. pthread_mutex_lock(&mutex);
    31. printf("child枷锁成功\n");
    32. pthread_mutex_unlock(&mutex);
    33. exit(0);
    34. }
    35. wait(NULL);
    36. printf("main over\n");
    37. exit(0);
    38. }

    结果

    分析

    代码从主程序开始执行,执行到线程函数时,创建线程,进入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, 错误返回错误码。
     

    1. pthread_mutex_t mutex;
    2. void fork_lock(void)
    3. {
    4. pthread_mutex_lock(&mutex);
    5. }
    6. void fork_unlock(void)
    7. {
    8. pthread_mutex_unlock(&mutex);
    9. }
    10. void * fun(void* arg)
    11. {
    12. pthread_mutex_lock(&mutex);
    13. printf("fun lock\n");
    14. sleep(5);
    15. pthread_mutex_unlock(&mutex);
    16. printf("fun unlock\n");
    17. }
    18. int main()
    19. {
    20. pthread_mutex_init(&mutex,NULL);
    21. pthread_t id;
    22. pthread_atfork(fork_lock,fork_unlock,fork_unlock);
    23. pthread_create(&id,NULL,fun,NULL);
    24. sleep(1);
    25. pid_t pid = fork();
    26. if ( pid == -1 )
    27. {
    28. exit(1);
    29. }
    30. if ( pid == 0 )
    31. {
    32. printf("child 准备lock\n");
    33. pthread_mutex_lock(&mutex);//阻塞
    34. printf("child加锁成功\n");
    35. pthread_mutex_unlock(&mutex);
    36. exit(0);
    37. }
    38. wait(NULL);
    39. printf("main exit\n");
    40. exit(0);
    41. }

    结果

    到这里线程的同步就更新这么多啦,明天更新生产者消费者模型。

  • 相关阅读:
    2022年Spring Cloud Alibaba快速上手教程
    python中的命名空间和变量作用域介绍
    代码随想录day46|139. 单词拆分
    RK3588平台开发系列讲解(视频篇)ffmpeg 的移植
    Tomcat相关概述和部署
    【微信小程序】flex布局
    图片怎么压缩大小?这样压缩图片很简单
    OPSF —— LSA-7 和 特殊区域(STUB区域&完全STUB区域 + NSSA区域&完全NSSA区域)
    这10款文案神器帮你速码,做自媒体还担心写不出文案吗?
    MySQL第六讲·where和having的异同?
  • 原文地址:https://blog.csdn.net/hello_world_juns/article/details/132991220