子进程似乎在一段时间循环中陷入睡眠

Posted

技术标签:

【中文标题】子进程似乎在一段时间循环中陷入睡眠【英文标题】:child process seems to get stuck in sleep in a while loop 【发布时间】:2021-09-02 19:15:25 【问题描述】:

我有一个 C 程序,它在循环中的某个点分叉一个子进程。子进程等待父进程完成其工作(一些数值计算)。如果出现问题,父进程中止,子进程应该从分叉时的状态继续,并通过一些修改重试计算。否则,父进程继续运行,子进程应该被杀死。

父子进程之间的通信是通过一个内存映射文件进行的,该文件只有1个字节作为一个字符,表示父进程的状态。

内存映射是这样完成的

    char child_flag[]="W";
    
    fp1 = fopen( "child_interface.dat","wb");
    // the interface file has two bytes, but only one is meaningful to the program
    fwrite(child_flag, 1, sizeof(child_flag), fp1); 
    fclose(fp1);
    printf("child_interface.dat created\n");
    
    if(mmap_child_flag() ==0) 
        printf("memory map of parent-child interface successful.\n");
        fflush(stdout);
    

子进程中的等待循环是这样的

child_pid = fork();                     
if (child_pid ==0)  /* child process, wait for parent process to finish*/

    mmap_child_flag();

    while(child_file[0]=='W' )  //Child waits
        usleep(100000);
    
    if(child_file[0]=='R') // run child process (as a new parent process)
        child_file[0]='W';
        goto label2;
    
    if(child_file[0]=='K') //Kill child process
        exit(0);
    


问题是子进程似乎陷入了睡眠 while 循环,即使父进程已将状态设置为“K”(检查了内存映射的文件)。这段代码已经在几台基于 linux 的超级计算机上运行过,行为似乎很不一致。在某些平台上,它可以顺利运行,但在另一些平台上,它会不断卡在 while 循环中。有时,如果我在调用 usleep 之后在 while 循环中添加一些语句,它就可以正常运行。

但是,我不确定 sleep while 循环是否是此问题的根本原因。我的猜测是,因为该进程除了检查内存中的一个字节之外几乎无事可做,因此系统让它一直处于休眠状态并以某种方式“忘记”让它检查内存。 Linux系统会不会发生这样的事情?

这是执行实际映射的函数

/* Memory map for parent-child processes interface */
int mmap_child_flag()

    int fd_child;    
    struct stat st_child; 
    
    // open files
    if ((fd_child = open("child_interface.dat", O_RDWR)) == -1)
        perror("open child_interface.dat");
        exit(1);
    
    // stat
    if (stat("child_interface.dat", &st_child) == -1)
        perror("stat of child_interface.dat");
        exit(1);
    
    // map, child_file is global char array
    child_file = mmap(0, st_child.st_size, PROT_WRITE, MAP_SHARED, fd_child, 0);
    if (child_file == (char *)(-1)) 
        perror("mmap child_interface.dat");
        exit(1);
    
    return 0;

【问题讨论】:

一旦子进程产生,它就会获得自己的child_file 数组副本。父母对自己的副本所做的任何事情都不会反映在孩子的副本中。您需要研究进程间通信技术来进行通信。或转到线程。 child_file 是如何设置在main 中的? @SGeorgiades 我已在说明中添加了此信息。我还忘了提到子进程在分叉后也会映射文件。 @Serge 我忘了提到子进程在分叉后也会重做接口文件的mmap。这会改变行为吗? @user556071 假设您在映射中使用MAP_SHARED 标志之一,它可以工作。您可能还需要使用msync() 【参考方案1】:

问题是子进程似乎陷入了睡眠 while 循环,即使父进程已将状态设置为“K”(检查了内存映射的文件)。

您的程序有几个奇怪的地方,其中之一是您完全使用共享内存来完成这项任务。请参阅下文了解更好的方法。

当前方法的问题

但是,就目前的问题而言,您遇到了同步问题。映射内存的内容正在子进程范围之外更改,但您没有理由怀疑可能是这种情况。因此,编译器可以假设,如果等待循环条件在第一次评估时得到满足,那么它也将在每次后续评估中得到满足。

对于更复杂的交互,您可能需要设置一个进程共享互斥锁或类似的来保护对共享内存的访问,但为此,将child_file 声明为指向 volatile char.

更好的方法

您希望孩子等待来自父母的一字节或两字节指令。您目前通过轮询共享内存段的内容来执行此操作,但正如您所发现的那样,设置和使用起来很复杂。使用管道将所需的信息从父母传递给孩子会容易得多:

设置:声明一个数组。致电pipe()。 子使用:子在管道上执行阻塞read()。 父级使用:write() 准备好后将消息发送到管道,然后将其关闭。或者直接关闭它。

请注意,管道本身会提供足够的同步,并且不需要等待循环。另请注意,孩子可以在不发送任何消息的情况下检测到父母死亡的情况,这是您的共享内存方法不支持的。

【讨论】:

大部分是正确的,除非如果循环中存在任何可能具有副作用的函数调用(例如任何 I/O 或usleep()),则编译器不能假定循环条件在后续评估中得到满足。 @G.Sliepen,编译器可以做各种各样的事情,包括一些令人惊讶的事情,有时甚至包括错误的事情。但是编译器可能会做的一件事是使用库函数的行为知识,无论是内置的还是以某种方式记录在函数声明中(例如 GNU C 函数属性),来确定给定函数不 有副作用。 @JohnBollinger 谢谢你的回答!为child_file 添加volatile 关键字就可以了。我在一个较小的部分进行了测试,同步似乎要快得多。所以这是编译器的“错误”,即没有实际检查内存中的值?我读了一些explanation about the volatile keyword,发现它确实是为这种情况设计的。我的另一个问题是,使用pipe() 会比mmap() 方法快(假设使用了volatile)? @user556071,除了更简单、更简洁,并且能够处理mmap() 无法处理的一些情况之外,管道解决方案也有可能更快。这是因为在 mmap 的情况下,当父级更新共享内存时,子级平均会通过usleep() 的一半,并且它将在检查更改之前完成睡眠。在管道案例中,孩子几乎可以立即做出反应。【参考方案2】:

共享内存区域有利于共享大量数据,但它是进程之间通信的糟糕方式。原因是您无法收到更改某些内容的通知,如果共享内存的其他用户死亡,您也不会收到通知。

要在两个进程之间进行通信,如果需要创建单向通信通道,请使用pipe(),或者如果需要双向通信,请使用socketpair()。您可以使用poll() 等待对方发送一些数据。如果对方的进程终止,您也会收到通知。

你正在使用这样的循环:

while(child_file[0]=='W' )  //Child waits
    usleep(100000);

这很糟糕,因为您平均浪费了 50 毫秒的时间来做一些有用的事情。除此之外,还有一个问题是编译器和 CPU 有时会改变写入内存的顺序。如果您在child_file 中的数据多于一开始的标志,那么这可能是个问题,除非您使用atomics 或显式障碍。

【讨论】:

以上是关于子进程似乎在一段时间循环中陷入睡眠的主要内容,如果未能解决你的问题,请参考以下文章

使用 python multiprocessing.Pool 进入睡眠状态的子进程

使用管道()从多个子进程中读取?

FastAPI 在子进程中从 websocket 发送

python 多进程和子进程1

多次调用std :: cout会使子进程挂起

子进程冻结 popen().stdout.read