POSIX Thread Synchronization and Inter-Process Communication

  1. Mutex Lock Implementation

A mutex (mutual exclusion lock) is used to protect shared resources from concurent access by multiple threads. When a thread needs to access a shared resource, it locks the mutex first, performs its operations, and then unlocks it.

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

int shared_data = 520;  // Shared resource

pthread_mutex_t lock;   // Mutex lock variable

// Worker thread 1
void* worker1(void* arg)
{
    printf("Thread 1 started\n");
    
    pthread_mutex_lock(&lock);    // Acquire lock
    shared_data = 1314;
    sleep(3);
    printf("Worker1: shared_data = %d\n", shared_data);
    pthread_mutex_unlock(&lock);  // Release lock
    
    return NULL;
}

// Worker thread 2
void* worker2(void* arg)
{
    printf("Thread 2 started\n");
    
    pthread_mutex_lock(&lock);    // Acquire lock
    shared_data++;
    sleep(1);
    printf("Worker2: shared_data = %d\n", shared_data);
    pthread_mutex_unlock(&lock);    // Release lock
    
    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_mutex_init(&lock, NULL);
    
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, worker1, NULL);
    pthread_create(&tid2, NULL, worker2, NULL);
    
    printf("Created threads: tid1=%#lx, tid2=%#lx\n", tid1, tid2);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    pthread_mutex_destroy(&lock);
    return 0;
}

  1. Unnamed Semaphore

Semaphores are counting synchronization primitives. They can be used to control access to shared resources or implement producer-consumer patterns.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

sem_t resource_sem;  // Counting semaphore

// Producer thread
void* producer(void* arg)
{
    int count = 5;
    while(count--)
    {
        sleep(1);
        printf("Produced one item\n");
        sem_post(&resource_sem);  // Release resource
    }
    pthread_exit(NULL);
}

// Consumer thread
void* consumer(void* arg)
{
    int count = 5;
    while(count--)
    {
        sem_wait(&resource_sem);  // Acquire resource
        printf("Consumed one item\n");
    }
    pthread_exit(NULL);
}

int main(int argc, char* argv[])
{
    sem_init(&resource_sem, 0, 0);  // Initialize for thread sharing, initial value 0
    
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, producer, NULL);
    pthread_create(&tid2, NULL, consumer, NULL);
    
    printf("Threads created: tid1=%#lx, tid2=%#lx\n", tid1, tid2);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    sem_destroy(&resource_sem);
    return 0;
}

  1. Sequential Thread Exceution Using Semaphores

By using multiple semaphores, we can control the execution order of threads. This example demonstrates three threads printing A, B, C in sequence.

#include <stdio>
#include <stdlib>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

sem_t sem_a, sem_b, sem_c;

// Thread A prints 'A'
void* thread_a(void* arg)
{
    int iterations = 5;
    while(iterations--)
    {
        sem_wait(&sem_a);
        usleep(100000);
        printf("A");
        fflush(stdout);
        sem_post(&sem_b);
    }
    return NULL;
}

// Thread B prints 'B'
void* thread_b(void* arg)
{
    int iterations = 5;
    while(iterations--)
    {
        sem_wait(&sem_b);
        usleep(100000);
        printf("B");
        fflush(stdout);
        sem_post(&sem_c);
    }
    return NULL;
}

// Thread C prints 'C'
void* thread_c(void* arg)
{
    int iterations = 5;
    while(iterations--)
    {
        sem_wait(&sem_c);
        usleep(100000);
        printf("C");
        fflush(stdout);
        sem_post(&sem_a);
    }
    return NULL;
}

int main(int argc, char* argv[])
{
    sem_init(&sem_a, 0, 1);  // Start with A
    sem_init(&sem_b, 0, 0);
    sem_init(&sem_c, 0, 0);
    
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, thread_a, NULL);
    pthread_create(&tid2, NULL, thread_b, NULL);
    pthread_create(&tid3, NULL, thread_c, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    
    sem_destroy(&sem_a);
    sem_destroy(&sem_b);
    sem_destroy(&sem_c);
    
    printf("\n");
    return 0;
}

  1. Condition Variables

Condition variables allow threads to wait for certain conditions to become true. They are always used with a mutex to insure atomic access to the shared state.

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

pthread_cond_t condition;
pthread_mutex_t mutex;

// Producer announces availability
void* producer(void* arg)
{
    int count = 5;
    while(count--)
    {
        sleep(1);
        printf("%#lx: Produced item\n", pthread_self());
    }
    pthread_cond_broadcast(&condition);  // Wake all waiting threads
    pthread_exit(NULL);
}

// Consumer waits for notification
void* consumer(void* arg)
{
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&condition, &mutex);  // Wait with lock
    printf("%#lx: Consumed item\n", pthread_self());
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

int main(int argc, char* argv[])
{
    pthread_cond_init(&condition, NULL);
    pthread_mutex_init(&mutex, NULL);
    
    pthread_t tid1, tid2, tid3, tid4, tid5, tid6;
    pthread_create(&tid1, NULL, producer, NULL);
    pthread_create(&tid2, NULL, consumer, NULL);
    pthread_create(&tid3, NULL, consumer, NULL);
    pthread_create(&tid4, NULL, consumer, NULL);
    pthread_create(&tid5, NULL, consumer, NULL);
    pthread_create(&tid6, NULL, consumer, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    pthread_join(tid4, NULL);
    pthread_join(tid5, NULL);
    pthread_join(tid6, NULL);
    
    pthread_cond_destroy(&condition);
    pthread_mutex_destroy(&mutex);
    return 0;
}

  1. Unnamed Pipe for Inter-Process Communication

Pipes provide a way for related processes to communicate. The pipe() system call creates a one-way communication channel.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
    int pipefd[2];
    if(pipe(pipefd) == -1)
    {
        perror("pipe");
        return -1;
    }
    printf("Read end: %d, Write end: %d\n", pipefd[0], pipefd[1]);
    
    pid_t pid = fork();
    if(pid > 0)
    {
        // Parent process - writer
        close(pipefd[0]);
        char buffer[128];
        while(1)
        {
            memset(buffer, 0, sizeof(buffer));
            fgets(buffer, sizeof(buffer), stdin);
            buffer[strlen(buffer)-1] = '\0';
            
            write(pipefd[1], buffer, strlen(buffer));
            
            if(strcmp(buffer, "quit") == 0)
                break;
        }
        close(pipefd[1]);
        wait(NULL);
    }
    else if(pid == 0)
    {
        // Child process - reader
        close(pipefd[1]);
        char buffer[128];
        while(1)
        {
            memset(buffer, 0, sizeof(buffer));
            read(pipefd[0], buffer, sizeof(buffer));
            printf("Received: %s\n", buffer);
            
            if(strcmp(buffer, "quit") == 0)
                break;
        }
        close(pipefd[0]);
        exit(EXIT_SUCCESS);
    }
    
    return 0;
}

  1. Signal Handling

Signals are asynchronous notifications sent to processes. The signal() function sets how a process handles specific signals.

6.1 Ignoring Signals

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    if(signal(SIGINT, SIG_IGN) == SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    
    while(1)
    {
        printf("Running forever...\n");
        sleep(1);
    }
    return 0;
}

6.2 Default Signal Handling

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    if(signal(SIGINT, SIG_DFL) == SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    
    while(1)
    {
        printf("Running forever...\n");
        sleep(1);
    }
    return 0;
}

6.3 Custom Signal Handler

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void custom_handler(int signum)
{
    if(signum == SIGINT)
    {
        printf("Caught Ctrl+C signal\n");
    }
}

int main(int argc, char* argv[])
{
    if(signal(SIGINT, custom_handler) == SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    
    while(1)
    {
        printf("Running forever...\n");
        sleep(1);
    }
    return 0;
}

Tags: pthread mutex Semaphore condition-variable Pipes

Posted on Wed, 04 Nov 2026 16:26:22 +0000 by mika79