Peter Ogden
Peter Ogden

Reputation: 720

Using pipes to communicate with children in multithreaded programs

I am trying to use fork to execute child programs from a multithreaded parent using code similar to:

#include <thread>
#include <unistd.h>
#include <vector>
#include <sys/wait.h>

void printWithCat(const std::string& data) {
    std::vector<char*> commandLine;
    // exec won't change argument so safe cast
    commandLine.push_back(const_cast<char*>("cat"));
    commandLine.push_back(0);

    int pipes[2];
    pipe(pipes);
    // Race condition here
    pid_t pid = fork();
    if (pid == 0) {
        // Redirect pipes[0] to stdin
        close(pipes[1]);
        close(0);
        dup(pipes[0]);
        close(pipes[0]);
        execvp("cat", &commandLine.front());
    }
    else {
        close(pipes[0]);
        write(pipes[1], (void*)(data.data()), data.size());
        close(pipes[1]);
        waitpid(pid, NULL, 0);
    }

}

int main()
{
     std::thread t1(printWithCat, "Hello, ");
     std::thread t2(printWithCat, "World!");

     t1.join();
     t2.join();
}

This code contains a race condition between the call to pipe and the call to fork. If both threads create pipes and then fork, each child process contains open file descriptors to both pipes and only close one. The result is that a pipe never gets closed and the child process never exits. I currently wrap the pipe and fork calls in a global lock but this adds an additional synchronisation. Is there a better way?

Upvotes: 2

Views: 1350

Answers (1)

Ben Voigt
Ben Voigt

Reputation: 283971

Don't think you're avoiding synchronization by avoiding a lock in your code -- the kernel is going to take locks for process creation anyway, probably on a far more global level than your lock.

So go ahead and use a lightweight mutex here.

Your problems are going to arise when different parts of the program make fork calls and don't agree on a single mutex (because some are buried in library code, etc)

Upvotes: 1

Related Questions