C programs run on a single thread by default - meaning only one instruction is executed at a time. But what if you need to perform multiple tasks simultaneously? For example, a graphical interface must remain responsive even while performing time-consuming operations in the background. This is where multithreading comes in.
Note: Multithreading is different from asynchronous programming, which allows a single thread to handle multiple tasks without blocking.
š§µ What Is a Thread?
A thread is a single sequence of instructions within a process. Each process starts with one thread, but it can create additional threads that run concurrently. These threads share the same memory space, making communication between them efficient, but also introducing risks like race conditions.
š§ Creating a Thread in C
Letās dive into how to use threads in C.
On POSIX systems (like Linux), the pthread
library provides thread functionality. To use it, you must link your program with -lpthread
. For example:
gcc -o main main.c -lpthread
Hereās a simple program that creates and runs a thread:
#include <pthread.h> #include <unistd.h> #include <stdio.h> void* wait_fn(void* arg) { sleep(2); printf("Done.\n"); return NULL; } int main() { pthread_t thread; int err = pthread_create(&thread, NULL, wait_fn, NULL); if (err != 0) { printf("An error occurred: %d\n", err); return 1; } printf("Waiting for the thread to end...\n"); pthread_join(thread, NULL); printf("Thread ended.\n"); return 0; }
Expected Output:
Waiting for the thread to end... Done. Thread ended.
Explanation:
-
pthread_create
starts a new thread and runs thewait_fn
function. -
pthread_join
blocks the main thread until the new thread finishes. - The
sleep()
call pauses the thread for 2 seconds to simulate work.
Pretty straightforward, right?
š Using Mutexes to Prevent Race Conditions
Multithreading introduces the risk of race conditions, especially when multiple threads access shared resources (e.g., a file or a variable). To prevent this, we use a mutex (short for mutual exclusion).
Hereās an example that shows how to safely increment a shared variable:
#include <pthread.h> #include <unistd.h> #include <stdio.h> pthread_mutex_t lock; int j; void* do_process(void* arg) { pthread_mutex_lock(&lock); int i = 0; j++; while (i < 5) { printf("%d", j); sleep(1); i++; } printf("...Done\n"); pthread_mutex_unlock(&lock); return NULL; } int main(void) { pthread_t t1, t2; if (pthread_mutex_init(&lock, NULL) != 0) { printf("Mutex initialization failed.\n"); return 1; } j = 0; pthread_create(&t1, NULL, do_process, NULL); pthread_create(&t2, NULL, do_process, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_mutex_destroy(&lock); return 0; }
Output:
11111...Done 22222...Done
The threads run sequentially because the mutex ensures only one thread can access the shared section at a time.
Tip: Even if you switch the order of
pthread_join
, the output will be the same because the threads themselves are synchronized by the mutex.
š¦ Semaphores: Flexible Synchronization
Semaphores are another synchronization mechanism, similar to mutexes, but more flexible. Unlike mutexes, semaphores donāt have ownership - any thread can lock or unlock them.
This makes semaphores suitable for managing access to a limited set of resources (like a pool of connections or permits).
We won't dive into full semaphore examples here, but if you're interested, check out the
semaphore.h
library and functions likesem_init
,sem_wait
, andsem_post
.
ā Wrapping Up
As youāve seen, multithreading in C is powerful yet surprisingly approachable. With just a few function calls, you can start writing concurrent programs - but always keep safety in mind with tools like mutexes and semaphores.
Top comments (17)
pthread is outdated since availability of C11 which introduced standard threading in C. The header files is
<threads.h>
with functions likethrd_create
.The standard functions for threading, conditions, and signalling, provide guarantees that pthreads cannot.
I have my answers,
threads.h
is better to use even if it's not POSIX (stackoverflow.com/a/9377007)pthread.h
is POSIX compliant,threads.h
isn't.But sure you can use it, it's implemented in linux and freeBSD kernels.
But
threads.h
is C11 compliant so by now ALL compilers have support for C11 at least for the three major ones:MSVC
GCC
CLANG
Yup, seems nice, their is just a lack of documentation, I wanted to know what did it really does.
You can find documentation here: en.cppreference.com/w/c/thread
Well, it's the C libraries for two of those platforms that are supposed to implement
threads.h
(even though I'm pretty sure glibc doesn't), although I'm not sure what Windows does.It's not the kernels, it's the C libraries. You can have a kernel installed and not be able to do anything without a C library.
Thank god for every C-related article here (for us, who have interest in low-level programming). Thanks, Nathanael!
C can be a very scary language at first but it's so captivating, I'm happy that you enjoyed reading this article š
Glab that you enjoyed it!
I'm starting to understand a lot about pure Computer Science since I got some courses about the theory of operating systems. It's so fascinating to learn how things really works beyond the compilers and why things are like that in programming languages!
Can you suggest that courses pls.
For the theory of operating systems, I see this subject at my school (engineering school), but for programming languages I heard that Engineering a Compiler is very great when starting in this domain.
The Dragon Book is very good but much more advanced.
Why can't you use a
for
loop?for( i = 0; i < 5; i++ )
You can and should do a for loop. It's been 1 year since I did this article, I think it's time to rewrite it with my current knowledge :)
Hey, recently I worked with the C++ standard thread library and made a series about it, where I've talked about the mutexes and locks. I hope you'll find it useful. dev.to/shreyosghosh/series/20850