• 【Linux】多线程中fork与互斥锁子进程会复制继承父进程里锁的状态


    摘自:https://blog.csdn.net/xiaoxiaoguailou/article/details/121617142

    问题提出:
    我们有这样一个问题:在一个多线程程序中创建子进程并且让子线程和子进程去获取一把全局变量的锁,输出子线程得到锁,然后解锁,子进程拿到锁,然后解锁;

    (一)初次尝试
    代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    
    pthread_mutex_t mutex;
    
    void* fun(void* arg)
    {
    pthread_mutex_lock(&mutex);
    printf("fun get lock\n");
    sleep(3);
    
    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)
    {
    perror("fork err");
    return -1;
    }
    
    if(pid == 0)
    {
    pthread_mutex_lock(&mutex);
    printf("child get lock\n");
    sleep(3);
    
    pthread_mutex_unlock(&mutex);
    printf("child unlock\n");
    exit(0);
    }
    
    wait(NULL);
    
    pthread_mutex_destroy(&mutex);
    printf("main over\n");
    return 0;
    }

    猜想结果:
    理一理思路:

    创建一把全局的锁,并初始化
    主线程创建子线程,去获取这把锁的同时,主线程休眠一秒,子线程得到这把锁,加锁fun get lock
    子线程休眠3秒,输出解锁fun unlock,子线程退出
    主线程开始fork,子进程得到这把锁,输出child lock
    子进程解锁输出child unlock
    父进程的主线程等待子进程的退出,最后销毁锁,输出main over
    所以…直接得到正确代码!!!
    (二)理性分析
    很遗憾,答案是错的!!!
    我们先来康康运行结果:正常输出了子线程的内容,但是。。。。卡住了?没错阻塞了。。

     再次分析:

    阻塞??难道是子进程获取锁时阻塞了?还是父进程等待子进程阻塞了?
    或者说:两处地方都阻塞了,子进程获取锁时阻塞,导致父进程阻塞。
    验证一下!!

    所以程序就是两处地方都阻塞了,子进程获取锁时阻塞,导致父进程阻塞。

    (三)解决问题
    实际上,我们子线程在获取这把锁并加锁后睡眠3秒,主线程睡眠1秒,在主线程1秒睡眠结束后,开始执行fork,此时的子线程还未解锁正处于睡眠状态,fork过程中,会直接复制父进程的所有资源(**包括这把锁、锁的状态**),没错就是有两把锁。此时子线程进行了加锁的状态,所以子线程复制的锁也是加锁状态。所以子线程会正常退出,而子进程会因为拿到加锁的锁而阻塞,父进程wait因为子进程阻塞而阻塞;

    (1)使用pthread_join()
    fork之前使用pthread_join()在子线程结束之前,主线程都将会阻塞,等待子线程结束后,在进行fork,此时子进程得到的锁就是解锁状态

    代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    
    pthread_mutex_t mutex;
    
    void* fun(void* arg)
    {
    pthread_mutex_lock(&mutex);
    printf("fun get lock\n");
    sleep(3);
    
    pthread_mutex_unlock(&mutex);
    printf("fun unlock\n");
    }
    int main()
    {
    pthread_mutex_init(&mutex, NULL);
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);
    pthread_join(id, NULL);
    
    sleep(1);
    
    pid_t pid = fork();
    if(pid == -1)
    {
    perror("fork err");
    return -1;
    }
    
    if(pid == 0)
    {
    pthread_mutex_lock(&mutex);
    printf("child get lock\n");
    sleep(3);
    
    pthread_mutex_unlock(&mutex);
    printf("child unlock\n");
    exit(0);
    }
    
    wait(NULL);
    
    pthread_mutex_destroy(&mutex);
    printf("main over\n");
    return 0;
    }

    结果:

    (2)使用phread_atfork()注册一个fork之前的判断
    头文件 : pthread.h
    int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

    prepare : fork执行之前调用该函数
    parent : fork执行之后父进程调用该函数
    child : fork执行之后子进程调用该函数
    返回值: 成功0,失败错误码
    代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    
    pthread_mutex_t mutex;
    
    void prepare_fun(void)
    {
    pthread_mutex_lock(&mutex);
    }
    
    void parent_fun(void)
    {
    pthread_mutex_unlock(&mutex);
    }
    
    void child_fun()
    {
    pthread_mutex_unlock(&mutex);
    }
    
    void* fun(void* arg)
    {
    pthread_mutex_lock(&mutex);
    printf("fun get lock\n");
    sleep(3);
    
    pthread_mutex_unlock(&mutex);
    printf("fun unlock\n");
    }
    
    int main()
    {
    
    pthread_mutex_init(&mutex, NULL);
    pthread_t id;
    pthread_atfork(prepare_fun, parent_fun, child_fun);
    pthread_create(&id, NULL, fun, NULL);
    
    sleep(1);
    
    pid_t pid = fork();
    if(pid == -1)
    {
    perror("fork err");
    return -1;
    }
    
    if(pid == 0)
    {
    pthread_mutex_lock(&mutex);
    printf("child get lock\n");
    sleep(3);
    
    pthread_mutex_unlock(&mutex);
    printf("child unlock\n");
    exit(0);
    }
    
    wait(NULL);
    
    pthread_mutex_destroy(&mutex);
    printf("main over\n");
    return 0;
    }


    结果:

    ========================= 文章转载后,我再送一种解决办法 =========================

    (3)我的方法更粗暴

    在fork()后的子进程中立即解锁(无论是否已上锁)

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    
    pthread_mutex_t mutex;
    
    
    void* fun(void* arg)
    {
    pthread_mutex_lock(&mutex);
    printf("fun get lock\n");
    sleep(3);
    
    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)
    {
    perror("fork err");
    return -1;
    }
    
    if(pid == 0)
    {
    pthread_mutex_unlock(&mutex); // 加此行防止进程复制过来时锁状态为"已上锁"

    pthread_mutex_lock(
    &mutex); printf("child get lock\n"); sleep(3); pthread_mutex_unlock(&mutex); printf("child unlock\n"); exit(0); } wait(NULL); pthread_mutex_destroy(&mutex); printf("main over\n"); return 0; }
  • 相关阅读:
    [LeetCode] Dungeon Game
    [LeetCode] Maximal Rectangle
    [LeetCode] Scramble String -- 三维动态规划的范例
    [LeetCode] Palindrome Partitioning II
    [LeetCode] Palindrome Partitioning
    [LeetCode] Interleaving String
    [LeetCode] Longest Valid Parentheses -- 挂动态规划羊头卖stack的狗肉
    JPA将查询结果转换为DTO对象
    EOS签名R值过大导致报错"is_canonical( c ): signature is not canonical"
    比特币中P2SH(pay-to-script-hash)多重签名的锁定脚本和解锁脚本
  • 原文地址:https://www.cnblogs.com/LiuYanYGZ/p/16719003.html
Copyright © 2020-2023  润新知