Synchronising N sibling processes after fork

2019-02-20 02:10发布

I'm having some hard time with synchronising N child process waiting each one of them to arrive at some specific point. I've tried semaphores and signals but I can't get my head around it.

#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) {;}

And I believe that the output should be (once the child are sync)

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

1条回答
手持菜刀,她持情操
2楼-- · 2019-02-20 03:04

Synchronization

There's a simple trick:

  • Create a pipe before you fork anything.
  • Have the children each close the write end of the pipe.
  • Have the children read from the pipe when you want to synchronize them.
  • Have the parent close both ends of the pipe when the children should be started.
  • Have the children close the read end of the pipe when they're released, so that the resources are released.
  • The children now do 'their thing' (grow up, produce output, die).
  • The parent now waits for its children to die (it's a morbid business when you're playing with processes on Unix).

If done correctly, the children all get EOF (zero bytes read) at the same time because there's no longer any process that can write to the pipe. (That's why it is important for the children to close the write end of the pipe before doing the synchronizing read().)

If you want the parent to know that the children are all ready, create two pipes before forking anything. The parent process closes the write end of this second pipe, and then reads from the read end. The children all close both ends of the pipe before settling into their read() call on the first pipe. The parent process gets EOF when all the children have closed the write end of the pipe, so it knows the children have all started, at least as far as closing the second pipe. The parent can then close the first pipe to release the children (and close the read end of the second pipe).

Don't wait too soon!

You are waiting in the default clause of the switch, which is not correct. You need all four child processes launched before you do any waiting — otherwise they'll never all be able to synchronize. When you do wait, you'll need to do your waiting in a (new) loop. And, while debugging, you should add print statements to identify what is going on in the parent process. For example, you'll print the status of the processes that exit, and their PID:

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

Working code

#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;
}

Sample run

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
查看更多
登录 后发表回答