💥 Copy that. Vault sequence continues.
We now enter:
🧨 [[asymmetric_pthreads/16_unsynchronized_stdout_race]]
“It printed… or did it? Why printf isn’t thread-safe — and why logs lie.”
🎯 GOAL
To show that even something as simple as printf() — without mutexes — leads to:
-
garbled output 🧻
-
missing logs 🫥
-
phantom races 😵
-
false confidence in your code 😬
All without crashing. Ever.
🔬 CONTEXT
Most devs think:
“My threads are working. I can see their logs.”
But in reality:
-
🧵 Multiple threads writing to
stdoutconcurrently -
❌ No locking around
printf -
💥 Output can overlap, get dropped, reordered, or mixed
📂 Source File: 16_unsynchronized_stdout_race.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define THREADS 10
#define LOOPS 10000
void *logger(void *arg)
{
int id = *(int *)arg;
char buf[128];
for (int i = 0; i < LOOPS; i++)
{
// Simulate a formatted message
snprintf(buf, sizeof(buf), "Thread %d reporting iteration %d\n", id, i);
printf("%s", buf); // 🔥 THIS IS UNSAFE
usleep(10 + rand() % 50);
}
return (NULL);
}
int main(void)
{
pthread_t t[THREADS];
int ids[THREADS];
srand(getpid());
for (int i = 0; i < THREADS; i++)
{
ids[i] = i;
if (pthread_create(&t[i], NULL, logger, &ids[i]) != 0)
{
perror("pthread_create failed");
exit(EXIT_FAILURE);
}
}
for (int i = 0; i < THREADS; i++)
pthread_join(t[i], NULL);
printf("\n✅ All threads completed\n");
return (0);
}🚨 OUTPUT EXAMPLE
Thread 1 reportThread 2 reporting iteration 40
ing iteration 39
Thread 3 reporting iteration 41
TThhrreeaadd 45 reporting iteration 42🎯 You didn’t write that. The OS did.
Because stdout was not synchronized.
💡 What You’re Really Learning
| 🧵 What You Do | 🔥 What Happens |
|---|---|
printf() in a loop | Race on shared I/O buffer |
| One thread writing at a time | 🧢 Not guaranteed — threads interleave at syscall layer |
| Output looks okay in small runs | 💣 But breaks at scale or under load |
| You trust logs to debug | ❌ But logs are not atomic |
✅ FIX VARIANT (Thread-Safe Logging)
Here’s what the FAANG++ fix looks like:
pthread_mutex_t print_lock = PTHREAD_MUTEX_INITIALIZER;
void thread_safe_log(const char *msg)
{
pthread_mutex_lock(&print_lock);
printf("%s", msg);
pthread_mutex_unlock(&print_lock);
}Then in your thread:
snprintf(buf, sizeof(buf), "Thread %d reporting iteration %d\n", id, i);
thread_safe_log(buf);🧠 Asymmetric Lessons
title: This Is a Silent Race
- It won’t crash
- It won’t segfault
- It will lie to you in productiontitle: Production Logging Rule
Always lock logs. Even for `printf()`. Especially for `printf()`.title: Why This Matters at 42
You’ll debug pipex, philosophers, or minishell — and wonder:
> “Why didn’t my debug log print?”
It’s not your logic.
It’s your **lack of synchronized output.**✅ Checklist
| ✅ Test | Description |
|---|---|
Unsynchronized printf() | 🧨 Output race |
| 10 threads | High collision chance |
| Randomized delay | Simulates real-world entropy |
| No crash | But data corruption |
| Teachable mutex fix | ✅ |
🔗 Related Vault Links
🔁 Next Iteration Options
-
Make output silently disappear using
fflush(stdout)traps -
Benchmark mutex-locked vs unlocked log speed
-
Add memory corruption via unsynchronized
sprintfto shared buffer -
Test what happens in pipe redirection (
> log.txt)
Say:
👉 “Next variant: stdout loss via pipe redirection”
or
👉 “Continue to 17: race on shared struct”
We’re in full asymmetric storm mode now.