Python IPC Pipes

Python IPC: Sending Data Through Pipes

In Python, when you’re working with multiple processes, sometimes they need to talk to each other — to send data, messages, or results. This communication between processes is called Inter-Process Communication, or IPC.

Python’s multiprocessing module gives us tools to handle IPC easily. One of the simplest ways to share data between two processes is by using a pipe.

A pipe is like a virtual tunnel: one end sends the data in, and the other end receives it. Python provides this through the multiprocessing.Pipe() function. It creates two connection objects that can both send and receive messages.

You will learn how to use pipes to send and receive data between two processes — using clear, hands-on code examples that are easy to follow and fun to build on.

Creating a Pipe

To create a direct connection between two processes in Python, you can use multiprocessing.Pipe(). This function returns two connection objects that represent the ends of a two-way communication channel. Think of these ends as paired telephone handsets — what one sends, the other can receive.

In the example below, the Pipe() call gives us parent_conn and child_conn. The parent process holds parent_conn, and the child process gets child_conn. The child uses .send() to send a message through the pipe, and the parent receives it using .recv(). After sending the message, the child closes its end of the pipe to free up resources.

from multiprocessing import Process, Pipe

def child(conn):
    conn.send("Hello from child!")  # Send message through the pipe
    conn.close()                    # Close connection when done


if __name__ == "__main__":

    parent_conn, child_conn = Pipe()  # Create the pipe

    p = Process(target=child, args=(child_conn,))
    p.start()

    message = parent_conn.recv()      # Receive message from child
    print(message)                    # Output: Hello from child!

    p.join()                          # Wait for child to finish

This simple example shows how one process can send a message to another using a pipe. The connection is reliable and synchronous — the recv() call waits until it receives something. It’s a clean, minimal way to start exploring how processes can talk to each other using shared channels.

Two-Way Communication

One powerful feature of Pipe() is that it creates a duplex connection by default. This means both ends of the pipe — the parent and the child — can send and receive messages. It’s not a one-way street; instead, it’s like handing walkie-talkies to both processes so they can talk to each other.

In the following example, the parent sends a message asking the child if it’s there. The child receives that message using .recv(), prints it, and then responds using .send(). The parent listens again and prints the child’s reply. This small back-and-forth shows how both processes can interact naturally and in sequence using a single pipe.

from multiprocessing import Process, Pipe

def child(conn):

    msg = conn.recv()
    print(f"Child got: {msg}")

    conn.send("Roger that, parent!")


if __name__ == "__main__":

    parent_conn, child_conn = Pipe()

    p = Process(target=child, args=(child_conn,))
    p.start()

    parent_conn.send("Are you there, child?")

    reply = parent_conn.recv()
    print(reply)

    p.join()

This demonstrates a full round-trip of communication. The pipe remains open while both ends are active, making it easy to coordinate logic between two processes. This setup can scale to more complex interactions where both processes need to share status or updates during a task.

Sending Multiple Data Items

Pipes are not limited to a single message—you can send as many as you need. Whether it’s numbers, strings, lists, or dictionaries, any picklable Python object can travel through a pipe. The two connected processes can exchange a whole stream of data, one item at a time.

In the example below, the parent process sends a series of messages labeled "message 0" through "message 4" to the child. Each message is received and printed by the child in a loop. To gracefully end the loop, the parent sends a special sentinel value: "STOP". The child checks for this value and exits the loop when it arrives.

from multiprocessing import Process, Pipe
import time

def child(conn):

    while True:

        try:

            data = conn.recv()

            if data == "STOP":
                break

            print(f"Child received: {data}")

        except EOFError:
            break


if __name__ == "__main__":

    parent_conn, child_conn = Pipe()

    p = Process(target=child, args=(child_conn,))
    p.start()

    for i in range(5):
        parent_conn.send(f"message {i}")
        time.sleep(0.2)

    parent_conn.send("STOP")

    p.join()

Using a sentinel value like "STOP" is a simple and reliable way to signal that no more data is coming. This allows the receiving process to shut down cleanly without needing a timeout or extra communication. It’s a clean exit strategy when sending multiple items through a pipe.

Passing Complex Python Objects

Pipes aren’t just for simple messages—they can carry full Python objects, as long as they can be pickled. This includes lists, dictionaries, and even custom class instances. This makes pipes very useful for sending structured data between processes, such as settings, user profiles, or task descriptions.

In the example below, the parent sends a dictionary containing a user’s name and score. The child receives the dictionary and accesses its fields directly. Because dictionaries are picklable, they travel through the pipe just like strings or numbers.

from multiprocessing import Process, Pipe

def child(conn):

    data = conn.recv()
    print(f"Received user: {data['name']} with score {data['score']}")


if __name__ == "__main__":

    parent_conn, child_conn = Pipe()

    p = Process(target=child, args=(child_conn,))
    p.start()

    parent_conn.send({"name": "Lucia", "score": 88})

    p.join()

This shows how easy it is to pass structured data using a pipe. You’re not limited to plain strings or numbers—any Python object that can be pickled can be transmitted between processes. This allows for rich communication using familiar data types.

One-Way Pipes (Simplex)

By default, a pipe created with multiprocessing.Pipe() allows two-way communication—both ends can send and receive. But sometimes, you only need one direction. For that, you can use a one-way (simplex) pipe by setting duplex=False. This setup is useful when one process should only send data and the other should only receive.

In the code below, the writer process sends a message using the write-only end of the pipe. The main process receives it using the read-only end. Since it’s a simplex pipe, trying to read from the sender or write to the receiver would raise an error.

from multiprocessing import Process, Pipe

# This function runs in a separate process and sends a message through the pipe
def writer(conn):
    conn.send("This is one-way only.")  # Send a single message
    conn.close()  # Close the connection after sending


if __name__ == "__main__":

    # Create a one-way (simplex) pipe: send_conn is for writing, recv_conn is for reading
    recv_conn, send_conn = Pipe(duplex=False)

    # Start a new process that will run the writer function
    p = Process(target=writer, args=(send_conn,))
    p.start()

    # Main process receives the message from the writer process
    print(recv_conn.recv())

    # Wait for the writer process to finish
    p.join()

This example shows how to clearly separate roles in communication: one process writes, the other reads. Simplex pipes help structure communication cleanly when you don’t need back-and-forth exchange.

Conclusion

In this article, we saw how Pipe() in Python’s multiprocessing module creates a communication link between two processes. Pipes allow sending and receiving data safely between processes, either in full-duplex mode—where both ends can send and receive messages—or in one-way (simplex) mode, where data flows in a single direction.

You can use pipes to build real-world scenarios like a chat system between client and server processes, command and control between a parent and worker, or data handoff from a producer to a processor. Exploring these examples will help you understand inter-process communication in practical terms and build more complex multiprocessing applications.

Scroll to Top