Understanding C's fork() through a simple example
Asked Answered
D

2

9
#include <stdio.h>
int num = 0;
int main(int argc, char*argv[]){
    int pid;
    pid = fork();
    printf("%d", num);  
    if(pid == 0){       /*child*/
        num = 1;
    }else if(pid > 0){  /*parent*/
        num = 2;
    }
    printf("%d", num);
}

I'm having trouble understanding why the possible outputs would be 0102 or 0012 or 0201 or 0021.

Here is what I (think) it should be producing. It hits the first printf statement and no matter what child or parent gets executed first, num hasn't been modified so 0 first. THEN next is either 1 or 2, then the next process executes so starts with 0 again (copied from the parent) and then either a 1 or 2 again. So the possible outputs should be:

0101 or 0102 or 0201 or 0202

Doe answered 7/3, 2013 at 4:4 Comment(3)
what is semicolon doing after argc. Its a syntax error.Subcontract
int main(int argc ,char*argv[]) this should be the mainSupplejack
You should be including #include <unistd.h>. It's the proper header file for fork().Competitor
C
10

In both the parent and the child, num is 0 for the first printf. In both the parent and the child, 0 is printed followed by the other value. In the parent process, the other value is 2. In the child process, the other value is 1.

However, the important thing to note is that although each process has an enforced order that zero has to be printed before the other number, there is no restriction on the printing of the two processes relative to each other.

Here's a real-life analogy: Suppose my coworker and each I leave work at the same time, stop at the grocery store, and then arrive home. We know I was at the store before I was at my home, and we know he was at the grocery store before he was at his home. But we don't know who was at the grocery store first, or who was at home first. We could each arrive at the grocery store around the same time and then each arrive home around the same time, or maybe he's delayed and I get the grocery store and home before he even gets to the store.

Something that won't happen is the printing of 1 or 2 more than once. Although after fork returns we have two processes running conceptually at once, and the timing of their events relative to each other is unspecified, the order of events in each process is well defined. Each process will set num to either 1 or 2 before printing it again, and because fork is defined to return 0 in the child and the child's pid in the parent, they will each set it to different values.

There is, actually, another reasonable output: 00. If fork is unable to create a new process, it returns -1. In this case program will print 0, the if and else ifs will fail because -1 is neither 0 nor greater than 0, num is not changed, and the program prints 0 again.

If you want to learn a lot about the definition of the ordering of effects in C programs, the key words to search for are "sequence points". In this program it's fairly straightforward (aside from the fact that we have two copies running at once), but it can sometimes be less obvious.

Claudelle answered 7/3, 2013 at 4:11 Comment(6)
.. Now explain mutual exclusion?!Teammate
"In the parent process, the other value is 2." ok, because pid > 0 if its the parent process. "In the child process, the other value is 0" what?! When we hit the child process, pid will equal to 0, thus changing the num to 1Doe
Right, so how could I have an output like 0012? or 0021?Doe
@SamuelEdwinWard - Your explanation was incredible good. I was guessing that Locke next question would be about mutual exclusion. In my own way I was trying to give you a compliment hence the +1Teammate
@LockeMcDonnell, imagine if both processes each take turns executing one line of code. This is only one of the many orders things could happen in, but it is one that would produce 0012 or 0021. One prints 0, then the other prints 0, then each prints its number.Claudelle
Since this is the accepted answer, I'd like it also explained briefly but explicitly why 0101 and 0202 mentioned in question are impossible.Chairmanship
S
9

This is not the problem with fork(). It is the printf() since printf() is buffered. Normally the buffer is flushed when it encounters a newline character at the end, '\n'. However since you have omitted this, the contents of the buffer stays and is not flushed. In the end, both processes (the original and the child) will have the output buffer with 0 or 1 in it. When it eventually gets flushed, you'll see this in both processes.

add fflush(stdout); after printf() and try.

Supplejack answered 7/3, 2013 at 4:15 Comment(4)
The stream printf outputs to is normally buffered but it isn't required to be. In any case, the relative orderings of the fflush calls between processes can't be guaranteed. Nor can it be guaranteed that the fflush will only result in one write call when the input is buffered, although that will almost certainly be the case with two characters in the buffer.Claudelle
If FILE buffer has just two bytes, I'm fairly sure it will result in one write system call on any libc commonly in use in PC. So while not guaranteed by any standard, still probably guaranteed by all existing code doing it so.Chairmanship
So, in practice, without adding flushing, outputs will be either 0102 or 0201. OTOH, in lightly loaded multicore system, if flushing is added, after first print, it'll be slow enough that other process gets to do it too, so it could be predicted that then output would often be 0012 or 0021. Conclusion: use synchronization if it matters.Chairmanship
@hyde, I agree that in the real world there's probably going to be one write call; I almost left that part out. Although now that I think about it, that write call could return 1.Claudelle

© 2022 - 2024 — McMap. All rights reserved.