/*
* Copyright ©2026 Soham Pardeshi. All rights reserved.
* Permission is hereby granted to students registered for University of
* Washington CSE 333 for use solely during Summer Quarter 2026 for
* purposes of the course. No other use, copying, distribution, or
* modification is permitted without prior written consent. Copyrights
* for third-party components of this work must be honored. Instructors
* interested in reusing these course materials should contact the author.
*/
// Exposes usleep() under strict -std=c17, which hides glibc extensions
// unless a feature test macro is defined before the system headers.
#define _DEFAULT_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define NUM_THREADS 5
// No free here: the caller does not own the memory being pointed at.
// Used by the buggy version below, where the pointer addresses a
// stack variable that main still controls.
//
// The short sleep is not part of the bug; it only widens the timing
// window so the race reliably shows up instead of getting lucky. main
// keeps looping and changing i while every worker is asleep here, so
// by the time a worker wakes up and reads *raw_number, i has usually
// already reached its final value.
static void* PrintNumber(void* raw_number) {
usleep(2000);
int number = *(int*) raw_number;
printf("%d\n", number);
return NULL;
}
// Frees the memory after reading it: the worker is the sole owner.
// Used by the fixed version below, where each pointer addresses a
// heap object created just for that thread.
static void* PrintNumberOwned(void* raw_number) {
int* number = raw_number;
printf("%d\n", *number);
free(number);
return NULL;
}
// BUG: every pthread_create call receives &i, the address of one
// shared loop variable. main keeps changing i while workers are still
// starting, so a worker can read a value that was never "assigned" to
// it, and more than one worker can read the same value.
static void RunBuggyVersion(void) {
printf("Buggy version (every worker points at the same &i):\n");
pthread_t threads[NUM_THREADS];
int i; // one int, reused on every iteration
for (i = 0; i < NUM_THREADS; i++) {
if (pthread_create(&threads[i], NULL, PrintNumber, &i) != 0) {
break;
}
}
for (int index = 0; index < NUM_THREADS; index++) {
pthread_join(threads[index], NULL);
}
}
// FIX: give every thread its own heap-allocated int. Each pointer
// addresses a distinct object that only one worker ever reads, so it
// does not matter that main keeps looping while workers are running.
static void RunFixedVersion(void) {
printf("Fixed version (each worker owns one heap int):\n");
pthread_t threads[NUM_THREADS];
int created = 0;
for (int value = 0; value < NUM_THREADS; value++) {
int* number = malloc(sizeof(*number));
if (number == NULL) {
break;
}
*number = value;
if (pthread_create(&threads[created], NULL, PrintNumberOwned,
number) != 0) {
free(number);
break;
}
created++;
}
for (int index = 0; index < created; index++) {
pthread_join(threads[index], NULL);
}
}
int main(void) {
// Run the buggy version first: expect skipped, repeated, or
// out-of-range numbers, and the exact output to vary between runs.
RunBuggyVersion();
RunFixedVersion();
return EXIT_SUCCESS;
}