π₯ Letβs dissect [[asymmetric_pthreads/05_join_vs_detach_threads]]
like a FAANG-grade concurrency surgeon β line by line, struct by struct, mutex by mutex.
𧬠File Dissection: [[05_join_vs_detach_threads]]
πΉ #define THREAD_COUNT 10
-
π Spawns 10 threads total
-
βοΈ Half are detached, half are joinable
-
π Even indexes β detached, odd β joinable
πΉ pthread_mutex_t print_mutex
-
Used in
safe_print()
-
Ensures logs donβt interleave mid-line
π‘ Without this, two threads calling
printf
may garble stdout
πΉ void safe_print(const char *fmt, ...)
pthread_mutex_lock(&print_mutex);
vprintf(fmt, args);
pthread_mutex_unlock(&print_mutex);
-
β Thread-safe printing
-
π Uses
va_list
+vprintf()
with lock wrapping -
π Reusable pattern in multi-threaded apps
πΉ char logs[MAX_LOGS][STR_BUFFER];
-
βοΈ Global log buffer
-
Logs up to
MAX_LOGS
strings (256 chars each)
πΉ int log_index + pthread_mutex_t log_mutex
-
π§ Guards the current index into the log array
-
Prevents threads from overwriting each otherβs logs
-
β Classic use-case for a shared resource lock
πΉ shared_counter
(β οΈ unprotected)
-
βοΈNot mutex-protected β race condition
-
Multiple threads read, modify, write
shared_counter
at once:
temp = shared_counter;
temp += 1;
usleep(100); // makes race more likely
shared_counter = temp;
- π₯ This is intentional: to show data races in practice
πΉ typedef struct s_thread_args
typedef struct s_thread_args
{
int index;
int delay;
int should_detach;
} t_thread_args;
-
β Compact thread argument struct
-
π§ͺ Each thread gets:
-
index
: thread ID (0β9) -
delay
: seconds tosleep()
-
should_detach
: whether topthread_detach
or not
-
π Lifecycle: worker()
Thread X started
-> logs PID + TID
-> sleep N seconds
-> increments shared_counter
-> logs end state
-> returns heap-allocated string (if joinable)
π₯ Highlights
-
pthread_self()
β logs internal thread ID -
add_log()
logs both start and end messages -
If joinable:
malloc()
a string and return it tomain
-
If detached: return is ignored (no memory leak because itβs never
malloc
ed for detached)
π§ π Main Thread Behavior
πΉ Phase 1: Launch Threads
args[i].should_detach = (i % 2 == 0); // Even β detach
if (args[i].should_detach)
pthread_detach(threads[i]);
else
pthread_join(threads[i], &res);
-
Threads created with shared
args[i]
-
Joinable ones will
malloc()
a return string βmain()
prints andfree()
s
πΉ Phase 2: Join Joinables
if (args[i].should_detach == 0)
{
pthread_join(...);
free(res);
}
-
Only join joinables
-
Print return string (e.g.
"Thread 3 result"
) -
free()
themalloc()
string safely
πΉ Phase 3: Final Log Dump
while (i < log_index)
printf("%s\n", logs[i]);
-
Prints global logs added by
worker()
-
Helps debug execution timeline
πΉ Final Output
printf("Final counter value (racy): %d\n", shared_counter);
-
Shows last value of the shared counter
-
Usually < THREAD_COUNT due to race condition
π§ Takeaways (FAANG-level Insight)
β What This Teaches:
π§ Concept | π Insight |
---|---|
Detach vs Join | Detached threads are βfire-and-forgetβ, unrecoverable |
Shared Structs | Safe if thread args are not overwritten (e.g., stack array, not heap) |
Logging | Mutex-wrapped add_log() ensures consistency |
Races | shared_counter is buggy by design β shows real-world risk |
Memory | Return values from threads must be free() d β or they leak |
Design | Logs + delays = controlled randomness, great for simulations |
π§ What You Could Improve or Extend
-
Add
pthread_mutex_t counter_mutex
to make counter race-free β -
Add
pthread_rwlock_t
and compare performance π -
Log timestamps to see thread overlap in real time β±οΈ
-
Use
pthread_attr_t
to set stack size or detach state explicitly π
Would you like me to refactor this into:
-
π§Ό
[[asymmetric_pthreads/06_mutex_vs_rwlock_under_load]]
(with mutex vs rwlock + safe counter)? -
or do you want to branch into atomic-only variants in
[[07_shared_counter_with_mutex]]
and[[08_atomic_counter_raceproof]]
first?