2

Currently working on some homework and having a hard time. The goal is to generate 100,000 numbers and add them all together by dividing the work into 10 processes (10,000 numbers each)

I think I've figured out how to fork processes (hopefully), but using Pipe() to relay the subtotals from each child process is not working... the program below returns 44901 for each child process and 449010 for the running total.

I'm struggling hard but I feel like this is something simple I should be able to understand.

main()
{
    int i;
    pid_t pid;
    int status = 0;
    int fd[2];
    int runningTotal = 0;
    pipe(fd);

    int t;
    int r;

    for (i = 0; i < 10; i++) {
        pid = fork();
        if (pid == 0){
            close(fd[0]);
            t = ChildProcess();
            write(fd[1], &t, sizeof(t));
            exit(0);
        }
        close(fd[1]);
        read(fd[0], &r, sizeof(r));
        runningTotal = runningTotal + r;
        wait(&status);
    }

    printf("%i\n", runningTotal);
}

int ChildProcess() {
    int i;
    int total = 0;
    int r = 0;

    for (i = 0; i < 10000; i++) {
        r = rand() % 10; // 0 to 10
        total = total + r;
    }

    printf("%i\n", total);
    return total;
}
Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
kang
  • 89
  • 10
  • 3
    First rule: when it says 100k in the question, demonstrate with 100 (or maybe 1000) first; get that working rock solid. Then think about multiplying by 1000 (or 100). – Jonathan Leffler Oct 05 '15 at 03:06
  • 1
    If your concern is that the children are all producing the same values, then the problem is that they're all using the same random sequence because you don't call `srand()` anywhere. You need to call it once per child, with a different seed for each child. – Jonathan Leffler Oct 05 '15 at 03:09
  • the declaration of the main() function ALWAYS returns 'int'. Your compiler should have complained about that problem. Always compile with all warnings enabled (for gcc, at a minimum use: `-Wall -Wextra -pedantic` ) fix the warnings – user3629249 Oct 05 '15 at 03:48
  • when posting code, especially when asking about a runtime problem, include the #include statements for the header files. Or do you expect us to guess what header files you include? – user3629249 Oct 05 '15 at 03:50
  • Please consistently indent the code for ease of reading by us humans. Do not use tabs for indenting as every word processor/editor has the tab stops/tab widths set differently. Suggest using 4 spaces as that does not 'eat up' the page width and is wide enough to be visible even with variable width fonts. Indent after every opening brace '{' un-indent before every closing brace '}'. It is also, for readability/understandability to separate code blocks (like if/else, do...while, for, while) with a blank line – user3629249 Oct 05 '15 at 03:54
  • the system function: `fork()` has three possible return states: pid == 0 means child is executing. pid> 0 means the parent is executing. pid < 0 means the call to `fork()` failed. The code needs to handle all three conditions. – user3629249 Oct 05 '15 at 03:58

3 Answers3

3

Initial diagnosis

If your concern is that the children are all producing the same values, then the problem is that they're all using the same random sequence because you don't call srand() anywhere. You need to call it once per child, with a different seed for each child.

It isn't 100% reliable, but you could probably get away with srand(time(0) + getpid()); in each child — or even just getpid() since those values are guaranteed to be different.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

int ChildProcess(void)
{
    int total = 0;
    srand(time(0) + getpid());

    for (int i = 0; i < 10000; i++)
    {
        int r = rand() % 10; // 0 to 9 (not 10).
        total = total + r;
    }

    printf("%i\n", total);
    return total;
}

Further scrutiny

Actually, on closer examination, there's another problem. The parent process closes the write end of the pipe after forking the first child, so the subsequent children don't have a usable file descriptor to use. The read value will always be the one from the first child. So, you need to do more serious work.

int main(void)
{
    int fd[2];
    pipe(fd);  // Missing error check

    for (int i = 0; i < 10; i++) {
        pid_t pid = fork();
        if (pid == 0){
            close(fd[0]);
            int t = ChildProcess();
            write(fd[1], &t, sizeof(t));  // Missing error check?
            exit(0);
        }
        // Print PID here?  Error check?
    }

    close(fd[1]);

    int r;
    int runningTotal = 0;
    while (read(fd[0], &r, sizeof(r)) > 0)  // Debugging opportunities here
        runningTotal = runningTotal + r;

    while (wait(0) > 0)  // Lots of debugging opportunities here
        ;

    printf("%i\n", runningTotal);
    return 0;
}
Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
3

Ordinarily, one would use a separate pipe for each child, for otherwise it's impossible for the parent to know from which process the data it reads comes. I don't think that's so much of an issue in this particular case, though, because here, you actually don't care. Although it still makes me cringe a bit, I think you indeed can get away with just one pipe for this particular task.

In fact, I don't think your problem is with the pipe at all. It is with rand(). All child processes compute exactly the same sequence of (pseudo-)random numbers because they all use the same (default) seed. If you want to produce different sequences of numbers, then you need to call srand() in each child process, giving a different seed in each one. The sequence of numbers rand() will generate is completely determined by the seed with which it starts.

Note, too, that if the system's random number generator is any good at all, then all the sums computed by the various processes should be very close to each other, and to the result you reported. This is a consequence of the Central Limit Theorem in statistics, but you can think of it simply as the larger results balancing the smaller ones on average. There's probably a slight bias arising from calculating the remainder mod 10.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 1
    Since the children are run lock-step (not in parallel), there's no problem sharing a pipe. There actually wouldn't be a problem even with the children running concurrently as long as each child writes a single `int` with a single `write()` and the parent reads the from the pipe a single `int` at a time. You could safely read multiple `int` values into an array if you were careful, but that is harder. – Jonathan Leffler Oct 05 '15 at 03:17
  • @JonathanLeffler, good points, thanks. I hadn't picked up on the fact that the children run sequentially, but of course you're right. – John Bollinger Oct 05 '15 at 03:39
1

given this code: (an excerpt from the posted code)

for (i = 0; i < 10; i++) {
    pid = fork();
    if (pid == 0){
        close(fd[0]);
        t = ChildProcess();
        write(fd[1], &t, sizeof(t));
        exit(0);
    }
    close(fd[1]);
    read(fd[0], &r, sizeof(r));
    runningTotal = runningTotal + r;
    wait(&status);
}

there is a sequence problem.

When the parent closes the fd[1] during the first iteration of the loop, that file descriptor does not 'magically' open again for the next iteration of the loop.

The code for the parent, in the loop, needs to check the returned value from the call to read() to assure the operation was successful. (it probably was not successful after the first iteration through the loop, so the variable 'r' will be unchanged.

user3629249
  • 16,402
  • 1
  • 16
  • 17
  • 1
    Reading from the pipe before the child finishes is fine; it blocks until either the child writes or the child dies and there is no process to write. In the original code, the parent closes the write end (vastly prematurely), so if the child dies without writing, the parent will get EOF (but won't notice that since it doesn't check the return from `read()`). – Jonathan Leffler Oct 05 '15 at 05:23