admin管理员组

文章数量:1405144

I have the following C code that writes to a file from both the parent and child process after a fork(). However, the output in testfile.txt is sometimes corrupted or in an unexpected order.

I'm attaching the code:

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

int main() {
    int fd = open("testfile.txt", O_WRONLY | O_CREAT, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    if (fork() == 0) { 
        // Child process
        write(fd, "Child\n", 6);
        close(fd);
    } else {
        // Parent process
        write(fd, "Parent\n", 7);
        close(fd);
    }

    return 0;
}

Issue:

  • The file sometimes contains "Child\nParent\n" and other times "Parent\nChild\n"
  • In some cases, the output is corrupted or mixed
  • I expected each process to write separately, but they seem to interfere with each other

Question:

  1. Why is this happening?
  2. How can I ensure correct, separate writes from each process?

I have the following C code that writes to a file from both the parent and child process after a fork(). However, the output in testfile.txt is sometimes corrupted or in an unexpected order.

I'm attaching the code:

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

int main() {
    int fd = open("testfile.txt", O_WRONLY | O_CREAT, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    if (fork() == 0) { 
        // Child process
        write(fd, "Child\n", 6);
        close(fd);
    } else {
        // Parent process
        write(fd, "Parent\n", 7);
        close(fd);
    }

    return 0;
}

Issue:

  • The file sometimes contains "Child\nParent\n" and other times "Parent\nChild\n"
  • In some cases, the output is corrupted or mixed
  • I expected each process to write separately, but they seem to interfere with each other

Question:

  1. Why is this happening?
  2. How can I ensure correct, separate writes from each process?
Share Improve this question asked Mar 8 at 19:44 Alphin ThomasAlphin Thomas 1 5
  • 2 That’s called a race condition. You have to do some sort of synchronization – Daniel A. White Commented Mar 8 at 19:47
  • The code is not thread-safe and apparently creates race condition. The term race condition is not so much of a term; rather, it is a widely accepted jargon expression. More formal term would be something like incorrect dependency on the order of execution. Or unwanted dependency... – Sergey A Kryukov Commented Mar 8 at 20:00
  • 1 @SergeyAKryukov 5.1.2.5 in the standard is called "Multi-threaded executions and data races" and in point 35: "The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior." - It doesn't describe what rules there are for processes competing for the same resource though since there is no concept of processes in the standard. So that's up to the OS. – Ted Lyngmo Commented Mar 8 at 20:21
  • @Ted Lyngmo — What you quoted (why?) has nothing to do with “rules”. What are you trying to say? – Sergey A Kryukov Commented Mar 8 at 21:49
  • 1 I quoted the formal definition of the term from the standard in response to your claim that it's merely jargon. – Ted Lyngmo Commented Mar 8 at 21:59
Add a comment  | 

2 Answers 2

Reset to default 3

Open the file with flag `O_APPEND` to avoid corruption:

The man page tells: "The file is opened in append mode. Before each write(2), the file offset is positioned at the end of the file, as if with lseek(2). The modification of the file offset and the write operation are performed as a single atomic step."

The output order is not controlled by this flag.

Your parent and children processes run concurrently without any measure in place to ensure synchronization of the write operation. This means that the order of the two writes will be arbitrary and can vary depending on which process is scheduled first by the system.

Moreover, the two write operations can occur at the same time and the content may get mixed. This is technically impossible on a POSIX compliant system for small writes like yours, because a write operation should be atomic wrt other concurrent writers for buffers up to PIPE_SIZE bytes. Nonetheless, for larger buffers it is a possibility.

If you want to avoid this, you will have to use some form of synchronization. Common ways to do this are:

  1. Shared memory, where you can define mutexes or semaphores.
  2. IPC (inter-process communication) via various means including other file descriptors (pipes, sockets, eventfd), signals, etc.
  3. File locks.

If you only want to ensure mutual exclusivity (i.e. no mixed content, one write will fully execute before the other) then a simple file lock via the flock syscall is enough. Technically this should already be the case without a lock because, as I said above, such small writes should be atomic on Linux.

Here's an example:

#include <err.h>
#include <sys/file.h>

/* ... */

    if (fork() == 0) {
        // Child process

        // Acquire exclusive lock
        if (flock(fd, LOCK_EX) != 0)
            err(1, "child: flock failed");

        write(fd, "Child\n", 6);

        // Closing automatically releases the lock
        close(fd);
    } else {
        // Parent process

        // Acquire exclusive lock
        if (flock(fd, LOCK_EX) != 0)
            err(1, "parent: flock failed");

        write(fd, "Parent\n", 7);

        // Closing automatically releases the lock
        close(fd);
    }

When one of the two processes is holding the lock, the other process will block on the flock call, which will only complete when the lock is released by the other process. This ensures that the critical sections between the flock and the close are executed. You will always see either Child\nParent\n or Parent\nChild\n, never some mixed content.


If instead you also want to guarantee the order of the two operations you will have to use something more complex. A pipe or an eventfd (the latter is Linux only) are some of the simplest tools you can use to achieve this.

Here's an example using a pipe created via the pipe syscall:

#include <err.h>
#include <unistd.h>

/* ... */
    
    // Create pipe for communication between parent and child
    int pipefds[2];
    if (pipe(pipefds) != 0)
        err(1, "pipe failed");

    if (fork() == 0) {
        // Child process

        // Wait for parent to write first
        char tmp;
        if (read(pipefds[0], &tmp, 1) != 1)
            err(1, "child: read from pipe failed");

        write(fd, "Child\n", 6);
        close(fd);
    } else {
        // Parent process

        write(fd, "Parent\n", 7);
        close(fd);

        // Signal child that it can now proceed
        if (write(pipefds[1], "x", 1) != 1)
            err(1, "parent: write to pipe failed");
    }

In this case you should always see Parent\nChild\n in your file.


As a final note: remember to always check the return value of fork, write and other syscalls or library function calls that can fail. A single write generally does not guarantee that the entire buffer is written, more than one write may be required.

本文标签: cWhy does my forked process sometimes overwrite data in a fileStack Overflow