【问题标题】:Synchronising N sibling processes after forkfork 后同步 N 个兄弟进程
【发布时间】:2019-05-13 00:39:11
【问题描述】:

我很难同步 N 个子进程,等待它们中的每一个到达某个特定点。 我已经尝试过信号量和信号,但我无法理解它。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/msg.h>

#define NUM_KIDS 4

void handle(int signum);

int main(int argc, char const *argv[])
{
    sem_t* sem;
    sem = sem_open("/ok", O_CREAT, 0);
    signal(SIGUSR1, handle);

    for(int i = 0; i < NUM_KIDS; i++) {
        switch(fork()) {
            case 0:
                fprintf(stderr, "ready %d from %d\n", getpid(), getppid());
                /* i would like that each child stop here untill everyone is ready */
                for(int j = 0; j < 10; j++) 
                fprintf(stderr, "lot of stuff\n");
                exit(0);
            break;
            default:
                /* unleashing the kids when everyone is ready */
                wait(NULL);
                fprintf(stderr, "OK\n");

            break;
        }
    }
    return 0;
}

void handle(int signum) {;}

而且我相信输出应该是(一旦孩子同步)

ready ... from xxx
ready ... from xxx
ready ... from xxx
ready ... from xxx
...lots of stuff... 10 times
...lots of stuff... 10 times
...lots of stuff... 10 times
...lots of stuff... 10 times

【问题讨论】:

  • 您的“几乎但不完全是 MCVE (minimal reproducible example)”代码中有许多多余的标头和一些多余的代码。信号量和信号代码似乎无关紧要。
  • 我尝试了很多解决方法,让它们放在那里更容易
  • 当你在你的机器上玩的时候这很好——当你在 SO 上提问时就不行了。你在 SO 上展示的代码应该是一个 MCVE——最小意味着“没有多余的代码”。

标签: c unix synchronization fork ipc


【解决方案1】:

同步

有一个简单的技巧:

  • 在分叉之前创建管道。
  • 让孩子们各自关闭管道的写入端。
  • 让孩子在您想要同步时从管道中读取。
  • 在应该启动子级时让父级关闭管道的两端。
  • 让孩子在释放时关闭管道的读取端,以便释放资源。
  • 孩子们现在做“他们的事”(长大、生产、死亡)。
  • 父级现在等待其子级死亡(当您在 Unix 上玩进程时,这是一个病态的事情)。

如果操作正确,子进程将同时获得 EOF(读取零字节),因为不再有任何进程可以写入管道。 (这就是为什么在同步 read() 之前关闭管道的写端对孩子来说很重要。)

如果您希望父级知道子级已准备就绪,请在分叉之前创建两个管道。父进程关闭第二个管道的写端,然后从读端读取。孩子们都关闭了管道的两端,然后才开始对第一个管道进行read() 呼叫。当所有子进程都关闭了管道的写入端时,父进程得到 EOF,因此它知道子进程已经全部启动,至少在关闭第二个管道时是这样。然后父级可以关闭第一个管道以释放子级(并关闭第二个管道的读取端)。

不要等得太早!

您在开关的default 子句中等待,这是不正确的。在进行任何等待之前,您需要启动所有四个子进程——否则它们将永远无法同步。当您等待时,您需要在(新)循环中进行等待。而且,在调试时,您应该添加打印语句来识别父进程中正在发生的事情。例如,您将打印退出进程的状态及其 PID:

int corpse;
int status;
while ((corpse = wait(&status)) > 0)
    printf("%d: child %d exited with status 0x%.4X\n", (int)getpid(), corpse, status);

工作代码

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

#define NUM_KIDS 4

int main(void)
{
    int p_pipe[2];
    int c_pipe[2];
    char c;
    if (pipe(p_pipe) != 0 || pipe(c_pipe) != 0)
    {
        fprintf(stderr, "Oops: failed to create pipes\n");
        return 1;
    }

    for (int i = 0; i < NUM_KIDS; i++)
    {
        switch (fork())
        {
        case 0:
            fprintf(stderr, "ready %d from %d\n", (int)getpid(), (int)getppid());
            close(p_pipe[0]);
            close(p_pipe[1]);
            close(c_pipe[1]);
            read(c_pipe[0], &c, 1);
            close(c_pipe[0]);
            for (int j = 0; j < 10; j++)
                fprintf(stderr, "lot of stuff\n");
            return NUM_KIDS + i;
        case -1:
            fprintf(stderr, "failed to fork child %d\n", i+1);
            return 1;
        default:
            break;
        }
    }

    close(p_pipe[1]);
    read(p_pipe[0], &c, 1);
    printf("%d: %d children started\n", (int)getpid(), NUM_KIDS);
    close(c_pipe[0]);
    close(c_pipe[1]);

    int corpse;
    int status;
    while ((corpse = wait(&status)) >= 0)
        printf("%d: child %d exited with status 0x%.4X\n", (int)getpid(), corpse, status);
    return 0;
}

样品运行

ready 81949 from 81948
ready 81950 from 81948
ready 81951 from 81948
ready 81952 from 81948
81948: 4 children started
lot of stuff
lot of stuff
lot of stuff
lot of stuff
…lines omitted for brevity…
lot of stuff
lot of stuff
lot of stuff
lot of stuff
81948: child 81951 exited with status 0x0600
81948: child 81952 exited with status 0x0700
81948: child 81950 exited with status 0x0500
81948: child 81949 exited with status 0x0400

【讨论】:

  • 可能我遗漏了一些东西我创建了 2 个 pippes 孩子:关闭第一个管道的 wirte,做它的事情,关闭第二个管道的两端,从第一个管道读取 //这里应该挂每个孩子,关闭读完第一管。父关闭写入端第二个管道,从第二个管道读取(这里应该挂起?),关闭读取端第二个管道,关闭写入端第一个管道。谢谢你的时间乔纳森
  • 查看带有代码和示例输出的更新答案。当然,您的 PID 会有所不同。
  • 天哪,非常感谢,那个该死的开关又搞砸了,再次感谢
  • 请注意,如果父级未能启动其中一个子级时,您不希望子级执行任何操作,请修改内容,以便子级注意来自read() 的返回值,如果它们得到 0,则不做任何事情,如果它们得到 1,则运行。然后,如果成功启动它们,则父级将每个子级写入一个字节到管道。只要每个孩子只尝试读取一个字节,一切都应该是同步的。
猜你喜欢
  • 2018-04-23
  • 1970-01-01
  • 1970-01-01
  • 2015-08-21
  • 2015-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多