【问题标题】:Calculating expression with multiple fork() children?用多个 fork() 子级计算表达式?
【发布时间】:2012-09-11 05:57:44
【问题描述】:

假设我们有表达式g=(a+b)*(c+d)-(e/f),其中变量的硬编码任意数字。我想使用多个子进程来计算这个表达式,以便更好地理解 fork() 的工作原理。

我的第一次尝试是在子 pid1 上计算 (a + b),在子 pid2 上计算 (c + d),在子 pid3 上计算 (e / f) ,然后在过程中进行加减运算。

好吧,令我失望的是,在子进程 pid1 中完成的(a + b) 计算并没有影响父进程中的double expression1 变量。我认为这背后的原因 - 每个 fork() 都会创建一个单独的内存空间;一旦子进程退出,在该子进程中完成的所有计算都将消失。

遇到这种情况,你通常会怎么做?我想也许我可以在一个子进程中嵌套 fork() 子进程来首先计算(a + b)等待;然后(c + d); 等待(e / f); 等待第一个孩子计算整个表达式;孩子 return(0); 父级终止。

但我认为这个问题有更简单的解决方案,对吗?

【问题讨论】:

  • 欢迎来到IPC的精彩世界——进程间通信。您对每个进程都有一个单独的地址空间的分析是正确的。您将不得不决定如何让每个子流程与相关的其他人进行通信。您可以使用共享内存、管道或各种其他技术。
  • 这种并行的常用技术是多线程,而不是多处理。所有线程共享相同的地址空间。

标签: c linux unix fork system-calls


【解决方案1】:

如果您坚持使用 fork() ,那么这是我现在使用子进程和共享内存的答案

请注意,exit() 在此处的使用方式与系统预期的方式相同:表示孩子是否正常退出。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>


struct shared{
    int a_b;
    int c_d;
    int e_f;
};
const int a=1,b=2,c=3,d=4,e=6,f=2;
const key_t key = 1234;
pid_t pab,pcd,pef;
void* shared_mem;

int main(){
    //Parent process create the shared memory 
    int shmid = shmget(key,sizeof(struct shared), 0666|IPC_CREAT);
    if(shmid == -1) exit(EXIT_FAILURE);

    //Fork child
    pab = fork();
    if(pab == 0){  
        //Inside process ab
        //attach to shared memory
        shared_mem = shmat(shmid,(void*) 0,0);
        if(shared_mem == (void*) -1) exit (EXIT_FAILURE);
        struct shared* shared_data = (struct shared*) shared_mem;
        shared_data->a_b = a +b;

        //detach
        if(shmdt(shared_mem) == -1) exit (EXIT_FAILURE);
        exit(EXIT_SUCCESS);
    }else {
        pcd = fork();
        if(pcd == 0){
            //Inside process cd
            //attach to shared memory
            shared_mem = shmat(shmid,(void*) 0,0);
            if(shared_mem == (void*) -1) exit (EXIT_FAILURE);
            struct shared* shared_data = (struct shared*) shared_mem;
            shared_data->c_d = c+d;

            //detach
            if(shmdt(shared_mem) == -1) exit (EXIT_FAILURE);
            exit(EXIT_SUCCESS);

        }else{
            pef = fork();
            if(pef == 0){
                //Inside process ef
                //attach to shared memory
                shared_mem = shmat(shmid,(void*) 0,0);
                if(shared_mem == (void*) -1) exit (EXIT_FAILURE);
                struct shared* shared_data = (struct shared*) shared_mem;
                shared_data->e_f = e/f;

                //detach
                if(shmdt(shared_mem) == -1) exit (EXIT_FAILURE);

                exit(EXIT_SUCCESS);
            }
        }
    }

    //Wait child process termination
    int status_ab,status_cd,status_ef;
    waitpid(pab,&status_ab,0);
    waitpid(pcd,&status_cd,0);
    waitpid(pef,&status_ef,0);

    //Check if all child exited  normally
    if(!WIFEXITED(status_ab) || !WIFEXITED(status_cd)||!WIFEXITED(status_ef)){
        exit(EXIT_FAILURE);
    }

    //Parent attaches to memory 
    shared_mem = shmat(shmid,(void*) 0,0);
    if(shared_mem == (void*) -1) exit (EXIT_FAILURE);
    struct shared* shared_data = (struct shared*) shared_mem;

    //Calculate result
    int result = (shared_data->a_b)*(shared_data->c_d)-(shared_data->e_f);
    printf("Result is %d\n", result);

    //Parent detaches from shared memory and deletes
    if(shmdt(shared_mem) == -1) exit (EXIT_FAILURE);
    if(shmctl(shmid,IPC_RMID,0) == -1) exit(EXIT_FAILURE);

    return EXIT_SUCCESS;

}

【讨论】:

    【解决方案2】:

    fork() 进程,然后 waitpid() 处理它们的返回值:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main()
    {
        //whatever values you like:
        int a = 1;
        int b = 2;
        int c = 3;
        int d = 4;
        int e = 15;
        int f = 6;
    
        int a_plus_b_pid;
        int c_plus_d_pid;
        int e_div_f_pid;
    
        int a_plus_b;
        int c_plus_d;
        int e_div_f;
    
        a_plus_b_pid = fork();
        if(a_plus_b_pid)
        {
            c_plus_d_pid = fork();
            if(c_plus_d_pid)
            {
                e_div_f_pid = fork();
                if (e_div_f_pid)
                {
                    //wait for our processes to exit, with our results, and stash the computed values.
                    waitpid(a_plus_b_pid, &a_plus_b, 0);
                    waitpid(c_plus_d_pid, &c_plus_d, 0);
                    waitpid(e_div_f_pid, &e_div_f, 0);
    
                    //The 8 least-significant bits carry info that we're not interested in here, so shift them out:
                    a_plus_b >>= 8;
                    c_plus_d >>= 8;
                    e_div_f >>= 8;
    
                    printf("%d %d %d %d\n", a_plus_b, c_plus_d, e_div_f, a_plus_b * c_plus_d - e_div_f);
                }
                else
                {
                    exit (e/f);
                }
            }
            else
            {
                exit (c+d);
            }
        }
        else
        {
            exit (a+b);
        }
    }
    

    【讨论】:

    • 我认为以不同的设计方式使用某些东西不是一个好主意。如果您调用 exit() 时使用不同的数字零,则表示您正在向操作系统发出您的程序失败的信号。另一方面,您将无法判断孩子返回的值是子表达式的结果还是错误代码。如果需要做IPC,就使用真正的IPC机制。
    • 我建议在这种情况下使用popen。它提供了一个简单的 IPC,足以满足您的需求。
    • exit的解释没有定义。操作系统不会对其值施加任何意义——只是按照惯例,0 是正常退出(因为假设只有一种好方法可以完成),其他任何东西都是异常的(因为有多种方法失败。)无论如何,它在这里几乎不适用,因为 OP 明确表示这个问题是学术性的。
    【解决方案3】:

    这是一个使用 pthreads 的版本:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    
    volatile int a_b;
    volatile int c_d;
    volatile int e_f;
    const int a=1,b=2,c=3,d=4,e=6,f=2;
    
    void* calc_ab(void*);
    void* calc_cd(void*);
    void* calc_ef(void*);
    
    int main(){
            pthread_t ab_thread, cd_thread, ef_thread;
    
            pthread_create(&ab_thread,NULL,calc_ab,NULL);
            pthread_create(&cd_thread,NULL,calc_cd,NULL);
            pthread_create(&ef_thread,NULL,calc_ef,NULL);
            pthread_join(ab_thread, NULL);
            pthread_join(cd_thread, NULL);
            pthread_join(ef_thread,NULL);
            int result = a_b*c_d-e_f;
            printf("Result is %d\n", result);
    
        return EXIT_SUCCESS;
    }
    
    void* calc_ab(void* arg){ a_b = a+b;pthread_exit(NULL);}
    void* calc_cd(void* arg){ c_d = c+d;pthread_exit(NULL);}
    void* calc_ef(void* arg){ e_f = e/f;pthread_exit(NULL);}
    

    要编译,你必须链接到 pthread: gcc pthread.c -lpthread -o teste

    备注

    • 请注意,在主线程和子线程之间共享的变量声明为volatile。这可以防止编译器进行一些内存优化,从而防止在一个线程中完成的写入操作不会被其他线程看到。
    • 每个子线程写入不同的共享变量。我想保持代码简单,不必显式处理同步。
    • 主线程只有在从更改它的线程的pthread_join 返回后才读取共享变量。同样,我想保持代码简单,不必显式处理同步。

    【讨论】:

      【解决方案4】:

      首先,您根本不需要进程来进行任意计算。嵌入解释器,例如lua 可能更简单。

      当然,每个进程都有自己的地址空间。键入 cat /proc/self/maps 以获取有关运行该 cat 命令的进程的信息。

      如果您坚持使用进程来了解它们如何通过管道进行通信,您可以使用 popen(3) 之类的东西,它将使用一些系统调用来启动和管道命令。

      char cmd[80];
      int a, b, sum;
      /// fill a & b
      snprintf (cmd, sizeof(cmd), "echo $[%d + %d]", a, b);
      FILE* pcmd = popen(cmd, "r");
      if (fscanf (pcmd, "%d", &sum)>0) {
          // do something with sum
      };
      pclose(pcmd);
      

      你应该读一本像Advanced Unix ProgrammingAdvanced Linux Programming这样的好书。真正的事情是了解syscalls,如fork(2)waitpid(2)execve(2)pipe(2)dup(2) 等......要了解syscalls(2) 是通过某些命令或程序完成的,请使用strace

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-10
        • 1970-01-01
        • 1970-01-01
        • 2020-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多