Create child processes to print lines from a file but last line disappeared without extra \n

Viewed 32

I have a problem when writing a C program to print out the lines of a file. Suppose the file has 5 lines, it should create 5 child processes. Each child process reads a line from the file and prints the respective line.

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

#define BUFSIZE 256
#define ITR 1e6

int main(int argc, char *argv[]) {
    if (argc != 2) exit(1);
    pid_t pid;
    char buffer[BUFSIZE] = { 0 };     // create buffer 
    FILE *fp = fopen(argv[1], "r"); // read file into *fp
    while (fgets(buffer, BUFSIZE, fp)) {
        pid = fork(); 
        if (pid == 0) {
            for (int j = 0; j < ITR; j++);
            printf("%s\n", buffer);
            _exit(0);               // exit child 
        } 
    }
    wait(NULL);                     // wait for child to finish
    return 0;
}

The original file is

Child 1 reads this line\n
Child 3 reads this line\n
Child 2 reads this line\n
Child 4 reads this line\n

The output is listed below.

Child 1 reads this line\n
\n
Child 2 reads this line\n
\n
Child 3 reads this line\n
\n
Child 4 reads this line\n
\n

However I don't want the extra \n between two lines. So I modified printf("%s\n", buffer); to printf("%s", buffer); so there would be only 1 \n as in the original file. But the output turns to be

Child 1 reads this line
Child 2 reads this line
Child 3 reads this line

The last line which should be Child 4 reads this line didn't print out as I expected. Does anyone know why this would happen?

1 Answers

Here are some problems you should investigate:

  • you should use exit(0) or return 0 instead of _exit(0): the non-standard function _exit(), equivalent to _Exit() on linux, might not properly flush standard stream buffers:

    7.22.4.5 The _Exit function
    Whether open streams with unwritten buffered data are flushed, open streams are closed, or temporary files are removed is implementation-defined.

  • the loop for (int j = 0; j < ITR; j++); has no side effect, so it is highly likely optimised out by the compiler.

  • stdout to a terminal is line buffered by default, so printf("%s\n", buffer) issues 2 separate system calls to flush the output to the system:

    1. one for the buffer contents that ends with a newline and
    2. on for the \n in the format string.

    These system calls might be interlaced between the different children processes. You should change the buffering method with setvbuf(stdout, NULL, BUFSIZ, _IOFBF) prior to the first output operation, in this case before the printf is sufficient.

Here is a modified version you can try:

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

#define BUFSIZE 256
#define ITR 1e6

int main(int argc, char *argv[]) {
    if (argc != 2) exit(1);
    pid_t pid;
    char buffer[BUFSIZE] = { 0 };     // create buffer 
    FILE *fp = fopen(argv[1], "r"); // read file into *fp
    int lineno = 1;
    while (fp && fgets(buffer, BUFSIZE, fp)) {
        pid = fork(); 
        if (pid == 0) {
            setvbuf(stdout, NULL, BUFSIZ, _IOFBF);
            printf("%d: %s\n", lineno, buffer);
            exit(0);               // exit child 
        }
        lineno++;
    }
    wait(NULL);                     // wait for child to finish
    return 0;
}

On my system, each line is output with an extra newline but not necessarily in the same order as the input file.

Related