Pipes and Thread in C Language

In UNIX-based operating systems, pipes are a powerful feature used for inter-process communication (IPC). They allow data to flow from one process to another in a unidirectional manner, effectively making the output of one process the input of another.

This mechanism is essential for creating complex workflows and is commonly used in shell scripting and process management.

Table of Content

Understanding Pipes & Threads in C Language

Key Concepts of pipe() & its Functions

What is a Pipe?

  • A pipe is a section of shared memory that processes use for communication.
  • It operates on a First-In-First-Out (FIFO) basis, ensuring that data is read in the same order it was written.
  • Pipes are unidirectional; data flows from the write end to the read end.

How Does pipe() Work?

  • The pipe() system call creates a pipe and returns two file descriptors:

    • fd[0]: The read end of the pipe.
    • fd[1]: The write end of the pipe.

Return Value:

  • Returns 0 on success.
  • Returns -1 on error.

Communication Between Processes

  • Pipes are typically used between related processes, such as a parent and its child created via fork().
  • When a process forks, the child inherits the parent’s file descriptors, including any pipes.
  • This inheritance allows the parent and child to communicate through the pipe.

Basic Pipe Usage Example

Let’s explore a simple example where a single process writes messages to a pipe and then reads them back.

Explanation

  • Creating the Pipe: The pipe(p) call initializes the pipe and assigns file descriptors to p[0] and p[1].

  • Writing to the Pipe: Three messages are written to the write end p[1].

  • Reading from the Pipe: A loop reads the messages from the read end p[0] and prints them.

Parent and Child Process Communication

To demonstrate inter-process communication, let’s see how a parent and child process can share a pipe.

Explanation

  • Pipe Creation: The parent process creates the pipe before calling fork().

  • Forking: The process splits into parent and child.

  • Parent Process:

    • Closes the read end of the pipe (p[0]) because it doesn’t need it.
    • Writes messages to the write end (p[1]).
    • Closes the write end after writing to signal EOF to the child.
    • Calls wait(NULL) to wait for the child process to complete.
  • Child Process:

    • Closes the write end of the pipe (p[1]) because it doesn’t need it.
    • Reads messages from the read end (p[0]) until EOF is reached.
    • Prints each message it receives.
    • Closes the read end and exits.

Important Points

  • Closing Unused Ends: It’s crucial to close the unused ends of the pipe in both the parent and child processes to prevent deadlocks and ensure proper behavior.
  • EOF Signaling: When the parent closes its write end, it signals EOF to the child, allowing the child’s read() call to return 0 and exit the loop.

==> Practice Exercise Question <==

Q1) Modify the previous example so that the child sends a message to the parent.

Q2) Write a C program that uses pipes for Inter-Process Communication (IPC) between a parent and child process. The parent process should prompt the user to input a number, write the number to the pipe, and then wait for the child process to complete. The child process should read the number from the pipe, calculate its factorial, and print the result. Ensure the unused ends of the pipe are closed in each process.

Code Example for Factorial Calculation using IPC (Pipe):

Threads & Their Functions

Creating a Default Thread

The pthread_create() function is used to create a new thread in a process. When you don’t specify any attributes (i.e., pass NULL), a default thread is created with attributes such as:

  • Unbounded
  • Non-detached (you can later use pthread_join() to wait for this thread to terminate)
  • Default stack size and stack
  • Inherits the parent’s priority

You can also use pthread_attr_init() to initialize a thread attribute object, which also creates a default thread when passed to pthread_create().

Basic syntax:

Here:

  • tid is where the new thread ID will be stored.
  • start_routine is the function the new thread will execute.
  • arg is the argument passed to start_routine.

Creating a Thread: pthread_create()

Waiting for a Thread to Terminate: pthread_join()

pthread_join() is used to wait for a thread to complete. It blocks the calling thread until the target thread specified by tid terminates.

This program waits for the thread to finish and then prints that it has completed.

Detaching a Thread: pthread_detach()

Instead of using pthread_join(), you can detach a thread using pthread_detach(). This means you don’t need to wait for the thread, and its resources will automatically be cleaned up when it finishes.

==> Practice Exercise Question <==

Q1) Write a C program that splits the task of summing an array of integers between multiple threads. The array will be divided into sections, and each thread will compute the sum of its assigned section. After all threads finish, the main thread should collect the partial sums and compute the total sum.

  • Use pthread_create() to create the threads.
  • Each thread should calculate the sum of a portion of the array.
  • Use pthread_join() to ensure the main thread waits for all threads to finish.
  • Print the total sum at the end.