- 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;
}
- 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;
}
- 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;
}
- 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;
}
- 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;
}
- 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;
}