C is the language of operating systems. Today you write programs that create processes, communicate between them, and use threads for concurrency — the same primitives that power every Unix-derived OS.
fork() creates an exact copy of the current process. The parent gets the child's PID; the child gets 0. exec() replaces the current process image with a new program. waitpid() reaps a child process, collecting its exit status and preventing zombie processes. The fork+exec pattern is how every shell launches programs. Pipes (pipe()) connect process stdout to another's stdin.
pthread_create() starts a new thread executing a function. pthread_join() waits for it to finish. Threads share the process's address space — which enables fast communication but requires synchronization. Mutex (pthread_mutex_t) ensures only one thread accesses shared data at a time. Condition variables (pthread_cond_t) let threads wait for events efficiently without busy-waiting.
Signals are asynchronous notifications: SIGINT (Ctrl+C), SIGSEGV (segfault), SIGTERM (kill), SIGCHLD (child exited). sigaction() installs handlers. Signal-safe functions are limited — you cannot safely call malloc or printf inside a signal handler. The self-pipe trick: write a byte to a pipe in the handler, read it in the main event loop. Signal handling is where many subtle C bugs live.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NTHREADS 4
#define ARRAY_SIZE 1000000
long data[ARRAY_SIZE];
long partial[NTHREADS];
typedef struct { int id; int start; int end; } Args;
void *sum_range(void *arg) {
Args *a = arg;
long s = 0;
for (int i = a->start; i < a->end; i++) s += data[i];
partial[a->id] = s;
return NULL;
}
int main(void) {
for (int i = 0; i < ARRAY_SIZE; i++) data[i] = i + 1;
pthread_t threads[NTHREADS];
Args args[NTHREADS];
int chunk = ARRAY_SIZE / NTHREADS;
for (int i = 0; i < NTHREADS; i++) {
args[i] = (Args){i, i*chunk, (i+1)*chunk};
pthread_create(&threads[i], NULL, sum_range, &args[i]);
}
for (int i = 0; i < NTHREADS; i++) pthread_join(threads[i], NULL);
long total = 0;
for (int i = 0; i < NTHREADS; i++) total += partial[i];
printf("Sum: %ld\n", total); // 500000500000
return 0;
}
// gcc -O2 -pthread threads.c -o threads
Implement a thread pool in C: a fixed number of worker threads that pull tasks from a thread-safe queue. Submit 100 tasks, each sleeping 10ms, and measure total runtime vs. a single-threaded approach.