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
Table of Contents
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.
#include
#include
#include // For exit()
#define MSGSIZE 16
int main() {
char* msg1 = "hello, world #1";
char* msg2 = "hello, world #2";
char* msg3 = "hello, world #3";
char inbuf[MSGSIZE]; // Buffer for incoming data
int p[2]; // Array to hold the two ends of the pipe
if (pipe(p) == -1) {
perror("pipe failed");
exit(1);
}
// Write messages to the pipe
write(p[1], msg1, MSGSIZE);
write(p[1], msg2, MSGSIZE);
write(p[1], msg3, MSGSIZE);
// Read messages from the pipe
for (int i = 0; i < 3; i++) {
read(p[0], inbuf, MSGSIZE);
printf("%s\n", inbuf);
}
return 0;
}
Explanation
Creating the Pipe: The
pipe(p)
call initializes the pipe and assigns file descriptors top[0]
andp[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.
#include
#include
#include // For exit()
#include // For wait()
#define MSGSIZE 16
int main() {
char* msg1 = "hello, child #1";
char* msg2 = "hello, child #2";
char* msg3 = "hello, child #3";
char inbuf[MSGSIZE];
int p[2];
pid_t pid;
if (pipe(p) == -1) {
perror("pipe failed");
exit(1);
}
pid = fork();
if (pid > 0) { // Parent process
close(p[0]); // Close unused read end
write(p[1], msg1, MSGSIZE);
write(p[1], msg2, MSGSIZE);
write(p[1], msg3, MSGSIZE);
close(p[1]); // Close write end after writing
wait(NULL); // Wait for child to finish
} else if (pid == 0) { // Child process
close(p[1]); // Close unused write end
while (read(p[0], inbuf, MSGSIZE) > 0) {
printf("Child received: %s\n", inbuf);
}
close(p[0]); // Close read end after reading
printf("Child finished reading\n");
exit(0);
} else {
perror("fork failed");
exit(1);
}
return 0;
}
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.
- Closes the read end of the pipe (
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.
- Closes the write end of the pipe (
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):
#include
#include
#include
int factorial(int n) {
int fact = 1;
for (int i = 1; i <= n; i++) {
fact *= i;
}
return fact;
}
int main() {
int fd[2]; // Array to hold the pipe descriptors
pid_t pid;
int number;
if (pipe(fd) == -1) {
printf("Pipe failed.\n");
return 1;
}
printf("Enter a number: ");
scanf("%d", &number);
pid = fork(); // Fork a child process
if (pid < 0) {
printf("Fork failed.\n");
return 1;
}
if (pid > 0) { // Parent process
close(fd[0]); // Close reading end of the pipe
write(fd[1], &number, sizeof(number)); // Write the number to the pipe
close(fd[1]); // Close writing end of the pipe
wait(NULL); // Wait for the child to finish
} else { // Child process
close(fd[1]); // Close writing end of the pipe
read(fd[0], &number, sizeof(number)); // Read the number from the pipe
close(fd[0]); // Close reading end of the pipe
int result = factorial(number); // Calculate factorial
printf("Factorial of %d is %d\n", number, result);
}
return 0;
}
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:
int pthread_create(pthread_t *tid, const pthread_attr_t *attr,
void start_routine, void *arg);
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 tostart_routine
.
Creating a Thread: pthread_create()
#include
#include
void* thread_function(void* arg) {
printf("Thread is running...\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_function, NULL);
pthread_join(tid, NULL);
return 0;
}
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.
#include
#include
void* thread_function(void* arg) {
printf("Thread is running...\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_function, NULL);
pthread_join(tid, NULL);
printf("Thread has finished.\n");
return 0;
}
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.
#include
#include
void* thread_function(void* arg) {
printf("Detached thread is running...\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_function, NULL);
pthread_detach(tid); // Detach the thread
printf("Main thread can exit without waiting for the detached thread.\n");
return 0;
}
==> 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.
Recent Comments
Categories
- Angular
- AWS
- Backend Development
- Big Data
- Cloud
- Database
- Deployment
- DevOps
- Docker
- Frontend Development
- GitHub
- Google Cloud Platform
- Installations
- Java
- JavaScript
- Linux
- MySQL
- Networking
- NodeJS
- Operating System
- Python
- Python Flask
- Report
- Security
- Server
- SpringBoot
- Subdomain
- TypeScript
- Uncategorized
- VSCode
- Webhosting
- WordPress
Search
Recent Post
Process scheduling algorithm – FIFO SJF RR
- 14 September, 2024
- 8 min read
How to Implement Multithreading in C Language
- 8 September, 2024
- 8 min read
How to Implement Inter-Process Communication Using Pipes
- 7 September, 2024
- 10 min read