zoukankan      html  css  js  c++  java
  • 线程与进程, 线程与信号

    线程与进程

    fork子进程如何复制多线程的父进程?

    考虑一个问题:父进程在fork之前,已经创建了多个线程,那么再调用fork,新建子进程具有和父进程同样数量的线程吗?是否会复制父进程的所有线程?

    答案是否定的。 fork子进程只会复制调用fork的线程,不会复制父进程的其他线程。既然是复制,因而子进程会自动继承父进程中的互斥锁(条件变量、信号量)的状态。i.e. 如果互斥量在父进程中被上锁,到子进程也会被上锁。

    然而,子进程并不清楚从父进程继承来的互斥量具体的锁状态,因为是复制的线程。互斥量有可能加锁,有可能解锁。如果已经加锁,子进程再次加锁,会导致永远无法解锁(死锁)。

    如下面的例子,子进程处于死锁状态

    /**
     * 线程与进程示例程序
     * fork子进程只会复制调用fork的执行线程,并不会复制父进程其他子线程。子进程自动>继承父进程中互斥锁(条件变量/信号量)的状态。父进程中已经加锁的互斥锁,子进程也>会被锁住
     */
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    pthread_mutex_t mutex;
    
    /* 父进程调用,会锁住父进程中的mutex*/
    void *another(void *arg)
    {
        printf("in child thread, lock the mutex
    ");
        pthread_mutex_lock(&mutex);
        sleep(5);
        pthread_mutex_unlock(&mutex);
    }
    
    int main()
    {
    
        pthread_mutex_init(&mutex, NULL);
        pthread_t id;
        pthread_create(&id, NULL, another, NULL);
    
        /* 父进程中的主线程暂停1s,以确保在执行fork前子线程已经开始运行并获得互斥量mutex */
        sleep(1);
        int pid;
    
        /* 由于前面已经sleep 1S,fork时互斥量mutex已经加锁,fork复制调用线程的锁状态,也是加锁的 */
        if ((pid = fork()) < 0) {
            perror("fork error");
            pthread_join(id, NULL);
            pthread_mutex_destroy(&mutex);
            return 1;
        }
        else if (pid == 0) { /* child */
            printf("i am in the child, want to get the lock
    ");
    
            /* 子进程从父进程继承来互斥锁mutex的状态,该互斥锁处于锁住状态,这是由父进程中的子线程执>行pthread_mutex_lock引起的。因此下面语句会导致子进程一直阻塞,而从逻辑上来说,虽然单独的子进程不>应该是阻塞的 */
            pthread_mutex_lock(&mutex);
            printf("I cannot run to here, oop...
    ");
            pthread_mutex_unlock(&mutex);
            exit(0);
        }
        else { /* parent */
            wait(NULL);
        }
        pthread_join(id, NULL);
        pthread_mutex_destroy(&mutex);
    
        return 0;
    }
    

    pthread_atfork 清理多线程父进程锁状态

    如何解决fork子进程继承父进程锁状态导致的死锁问题?
    之前这篇文章Linux 系统编程学习笔记 - 线程,有提到过pthread_atfork函数,可以建立3个fork句柄帮助清理互斥锁的状态,以确保fork调用之后父进程、子进程都有一个确定的锁状态。

    pthread_atfork解决fork子进程锁状态问题,示例程序:

    /**
     * 线程与进程示例程序
     * 利用pthread_atfork解决fork子进程继承父进程锁状态,而不清楚锁状态的问题
     */
    
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    pthread_mutex_t mutex;
    
    /* 父进程调用,会锁住父进程中的mutex*/
    void *another(void *arg)
    {
        printf("in child thread, lock the mutex
    ");
        pthread_mutex_lock(&mutex);
        sleep(5);
        pthread_mutex_unlock(&mutex);
    }
    
    /* fork调用创建出子进程之前被执行,用来锁定父进程中所有的互斥锁 */
    void prepare()
    {
        pthread_mutex_lock(&mutex);
    }
    
    /* fork调用创建出子进程之后,释放父、子进程中被锁主的互斥锁 */
    void infork()
    {
        pthread_mutex_unlock(&mutex);
    }
    
    int main()
    {
        pthread_mutex_init(&mutex, NULL);
        pthread_t id;
        pthread_create(&id, NULL, another, NULL);
    
        /* 父进程中的主线程暂停1s,以确保在执行fork前子线程已经开始运行并获得互斥量mutex */
        sleep(1);
        int pid;
    
        pthread_atfork(prepare, infork, infork);
    
        /* 由于前面已经sleep 1S,fork时互斥量mutex已经加锁,fork复制调用线程的锁状态,也是加锁的 */
        if ((pid = fork()) < 0) {
            perror("fork error");
            pthread_join(id, NULL);
            pthread_mutex_destroy(&mutex);
            return 1;
        }
        else if (pid == 0) { /* child */
            printf("i am in the child, want to get the lock
    ");
    
            /* 子进程从父进程继承来互斥锁mutex的状态,该互斥锁处于锁住状态,这是由父进程中的子线程执>行pthread_mutex_lock引起的。因此下面语句会导致子进程一直阻塞,而从逻辑上来说,虽然单独的子进程不>应该是阻塞的 */
            pthread_mutex_lock(&mutex);
            printf("I cannot run to here, oop...
    ");
            pthread_mutex_unlock(&mutex);
            exit(0);
        }
    else { /* parent */
            wait(NULL);
        }
    
        pthread_join(id, NULL);
        pthread_mutex_destroy(&mutex);
    
        return 0;
    }
    

    线程与信号

    线程的signal mask

    每个线程都拥有自己独立的信号屏蔽字(signal mask),共享信号处理以及信号处理函数。
    i.e. 线程可以选择是否屏蔽信号,但是捕获方式(SIG_DFL, SIG_IGN或捕获)是共享的,捕获函数也是共享的。

    可以用sigprocmask设置进程的signal mask,如何设置线程的signal mask?
    注:sigaction修改的是进程对应某个信号的捕获函数,以及处理信号期间的signal mask,而非任意情况下的signal mask。

    线程使用pthread_sigmask设置线程的signal mask,sigwait阻塞线程等待指定信号并处理之。
    参见Linux 系统编程学习笔记 - 线程

    实例:一个线程处理所有信号

    如果每个线程都单独处理信号,可能会有不同的信号处理方式,不同的signal mask,很容易导致逻辑错误。如果让一个专门的线程处理所有信号,如何实现?

    步骤:
    1)主线程创建出子线程之前,就调用pthread_sigmask设置好signal mask,屏蔽(阻塞)所有信号。这样,新建的所有子线程会自动继承这个signal mask,从而不会响应被屏蔽信号。

    2)在某个线程中,调用sigwait等待信号并处理

    注意:一旦使用sigwait,就不应该在外对应信号设置信号处理函数,因为程序接收到信号时,只能有一个起作用。

    一个专门的线程处理指定信号示例

    #include <signal.h>
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    
    #define handle_error_en(en, msg) 
        do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
    
    void *sig_thread(void *arg)
    {
        sigset_t *set = (sigset_t *)arg;
        int s, sig;
        for (; ;) {
            /* 2. 调用sigwait等待信号 */
            s = sigwait(set, &sig);
            if (s != 0) {
                handle_error_en(s, "sigwait error");
            }
            printf("Signal handling thread got signal %d
    ", sig);
        }
    }
    
    int main()
    {
        pthread_t thread;
        sigset_t set;
        int s;
    
        /* 1. 在主线程中设置signal mask */
        sigemptyset(&set);
        sigaddset(&set, SIGQUIT);
        sigaddset(&set, SIGINT);
        sigaddset(&set, SIGUSR1);
        s = pthread_sigmask(SIG_BLOCK, &set, NULL);
        if (s != 0)
            handle_error_en(s, "pthread_sigmask error");
    
        s = pthread_create(&thread, NULL, &sig_thread, (void *)&set);
        if (s != 0)
            handle_error_en(s, "pthread_create error");
    
        pause();
        return 0;
    }
    

    从运行结果,可以看到,即使线程也继承了main线程的signal mask屏蔽了信号,但是依然可以用sigwait接收到信号并处理之。

  • 相关阅读:
    怎样监听HTTP请求的发出与完成
    在Ubuntu下安装source Insight
    Android 5.1 预制输入法
    RK3288编译 Android 5.1 固件
    Android编程之Listener侦听的N种写法及实现原理
    android thread Runnable
    Android Service完全解析(下)
    Android Service完全解析(上)
    android 串口 android-serialport-api
    Android Studio在Ubuntu下离线安装Gradle
  • 原文地址:https://www.cnblogs.com/fortunely/p/15044854.html
Copyright © 2011-2022 走看看