C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
136: Mutexes and Locking with C11 Threads
Why can't I just use a global variable for my counter?
I get this question a lot. It seems intuitive: if you have a global int total_requests, and ten threads increment it, you should end up with ten. But C doesn't work that way at the hardware level. An operation like total_requests++ isn't a single step; it's actually three: load the value from memory into a register, increment it, and store it back.
If Thread A loads the value (say, 5) and is then interrupted by Thread B, which also loads 5, increments it to 6, and stores it... Thread A still thinks the value is 5. It increments its local register to 6 and stores it. You just lost an entire increment. We call this a race condition, and it's the absolute worst kind of bug because it's non-deterministic. Your code might work perfectly on your laptop but crash or give wrong totals on a production server with more cores.
What's the actual workflow for using mtx_t?
In C11, we use the mtx_t type from threads.h. Think of a mutex (short for mutual exclusion) as a physical key to a room. If you have the key, you can enter the critical section of your code. If someone else has it, you have to wait in line until they come out and hand it over.
Here is how you actually implement it. I'll use a simple shared counter for a simulated web server:
#include <stdio.h>
#include <threads.h>
int request_count = 0;
mtx_t count_mutex;
int handle_request(void* arg) {
// Lock the mutex before touching the shared variable
if (mtx_lock(&count_mutex) == thrd_success) {
request_count++;
mtx_unlock(&count_mutex);
}
return 0;
}
int main() {
// Initialize the mutex
if (mtx_init(&count_mutex, mtx_plain) != thrd_success) {
return 1; // Initialization failed
}
thrd_t t1, t2;
thrd_create(&t1, handle_request, NULL);
thrd_create(&t2, handle_request, NULL);
thrd_join(t1, NULL);
thrd_join(t2, NULL);
printf("Total requests: %d\n", request_count);
// Always clean up your resources
mtx_destroy(&count_mutex);
return 0;
}
Notice that I used mtx_plain. That's the standard, non-recursive mutex. The most important part here is that the time spent holding the lock is tiny. Never put a printf or a heavy network call inside a lock if you can avoid it, or you'll turn your multi-threaded program into a single-threaded one because every thread is just waiting in line.
How do I stop my program from hanging forever?
You've probably heard the term "deadlock." This usually happens in one of two ways. First, the "forgotten unlock." If your function returns early due to an error but you forgot to call mtx_unlock, every other thread that ever tries to touch that variable will hang indefinitely. I highly recommend keeping your lock/unlock pairs as close together as possible.
The second way is the "deadly embrace." This happens when Thread A locks Mutex 1 and waits for Mutex 2, while Thread B has locked Mutex 2 and is waiting for Mutex 1. Neither can move.
If you're in a situation where you can't afford to wait, C11 gives you mtx_trylock. Instead of putting the thread to sleep, it returns immediately. If it gets the lock, it returns thrd_success; if it's already locked, it returns thrd_busy. This lets you do other work or try again later instead of freezing your entire application.
📋 Practical Task
Implement a Thread-Safe Shared Log Buffer
You are building a logging system where multiple worker threads write messages to a shared global string buffer. Without synchronization, the messages will overlap and corrupt the text.
- Create a global
char log_buffer[1024]and a globalint buffer_index = 0. - Initialize a
mtx_tmutex to protect these two variables. - Write a function
void write_log(const char* msg)that locks the mutex, appends the message to the buffer usingstrcatorsprintf(while ensuring you don't exceed the 1024 limit), updates thebuffer_index, and then unlocks the mutex. - Spawn three threads that each call
write_logfive times with different messages. - Join the threads and print the final buffer to the console to verify that no messages were mangled.
There are no comments for now.