but those functions also use stack space, overwriting your shared data value with garbage
This is indeed exactly what was happening. Thanks!
Usually you want some kind of signal, be it win32 event, or semaphore, or futex, or whatever to be signaled for all threads - many options to choose from.
I tried out some different ways of implementing this signalling. Here's the minimal set of examples I came up with, which uses an Event, a Condition Variable, and WaitOnAddress()
to coordinate termination of three respective groups of sub threads:
// Three different ways of signalling groups of sub threads to self-terminate.
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// For WakeByAddressAll, WaitOnAddress.
#pragma comment(lib, "synchronization")
#define THREAD_WAIT_TIMEOUT_MS 100
static uint32_t should_terminate_initial_value = 0;
struct SharedData
{
uint32_t should_terminate;
CONDITION_VARIABLE cv;
SRWLOCK srw;
HANDLE event;
};
struct ThreadData
{
struct SharedData *shared;
uint32_t thread_id;
};
DWORD WINAPI thread_notified_with_event(LPVOID lpParam)
{
struct ThreadData *data = (struct ThreadData *)lpParam;
struct SharedData *shared = data->shared;
printf("thread[%d]: start\n", data->thread_id);
while (1)
{
if (WaitForSingleObject(shared->event, THREAD_WAIT_TIMEOUT_MS) == WAIT_OBJECT_0)
{
break;
}
}
printf("thread[%d]: end\n", data->thread_id);
return 0;
}
DWORD WINAPI thread_notified_with_condition_variable(LPVOID lpParam)
{
struct ThreadData *data = (struct ThreadData *)lpParam;
struct SharedData *shared = data->shared;
printf("thread[%d]: start\n", data->thread_id);
while (1)
{
AcquireSRWLockShared(&shared->srw);
if (shared->should_terminate)
{
ReleaseSRWLockShared(&shared->srw);
break;
}
if (!SleepConditionVariableSRW(&shared->cv, &shared->srw, THREAD_WAIT_TIMEOUT_MS, CONDITION_VARIABLE_LOCKMODE_SHARED))
{
assert(GetLastError() == ERROR_TIMEOUT);
}
ReleaseSRWLockShared(&shared->srw);
}
printf("thread[%d]: end\n", data->thread_id);
return 0;
}
DWORD WINAPI thread_notified_with_address(LPVOID lpParam)
{
struct ThreadData *data = (struct ThreadData *)lpParam;
struct SharedData *shared = data->shared;
printf("thread[%d]: start\n", data->thread_id);
while (1)
{
AcquireSRWLockShared(&shared->srw);
if (shared->should_terminate)
{
ReleaseSRWLockShared(&shared->srw);
break;
}
ReleaseSRWLockShared(&shared->srw);
if (!WaitOnAddress(&shared->should_terminate, &should_terminate_initial_value, sizeof(shared->should_terminate), THREAD_WAIT_TIMEOUT_MS))
{
assert(GetLastError() == ERROR_TIMEOUT);
}
}
printf("thread[%d]: end\n", data->thread_id);
return 0;
}
int main(void)
{
struct SharedData shared_data =
{
.should_terminate = should_terminate_initial_value,
.event = CreateEvent(NULL, TRUE, FALSE, NULL),
};
InitializeSRWLock(&shared_data.srw);
InitializeConditionVariable(&shared_data.cv);
HANDLE subthreads[] =
{
CreateThread(NULL, 0, &thread_notified_with_event, &(struct ThreadData){.thread_id = 0, .shared = &shared_data}, 0, NULL),
CreateThread(NULL, 0, &thread_notified_with_event, &(struct ThreadData){.thread_id = 1, .shared = &shared_data}, 0, NULL),
CreateThread(NULL, 0, &thread_notified_with_event, &(struct ThreadData){.thread_id = 2, .shared = &shared_data}, 0, NULL),
CreateThread(NULL, 0, &thread_notified_with_condition_variable, &(struct ThreadData){.thread_id = 3, .shared = &shared_data}, 0, NULL),
CreateThread(NULL, 0, &thread_notified_with_condition_variable, &(struct ThreadData){.thread_id = 4, .shared = &shared_data}, 0, NULL),
CreateThread(NULL, 0, &thread_notified_with_condition_variable, &(struct ThreadData){.thread_id = 5, .shared = &shared_data}, 0, NULL),
CreateThread(NULL, 0, &thread_notified_with_address, &(struct ThreadData){.thread_id = 6, .shared = &shared_data}, 0, NULL),
CreateThread(NULL, 0, &thread_notified_with_address, &(struct ThreadData){.thread_id = 7, .shared = &shared_data}, 0, NULL),
CreateThread(NULL, 0, &thread_notified_with_address, &(struct ThreadData){.thread_id = 8, .shared = &shared_data}, 0, NULL),
};
size_t subthreads_count = sizeof(subthreads) / sizeof(subthreads[0]);
printf("main: sleep\n");
Sleep(2000);
printf("main: signal termination\n");
AcquireSRWLockExclusive(&shared_data.srw);
shared_data.should_terminate = 1;
ReleaseSRWLockExclusive(&shared_data.srw);
SetEvent(shared_data.event);
WakeAllConditionVariable(&shared_data.cv);
WakeByAddressAll(&shared_data.should_terminate);
WaitForMultipleObjects(subthreads_count, subthreads, TRUE, INFINITE);
printf("main: subthreads terminated\n");
for (size_t i = 0; i < subthreads_count; ++i)
{
CloseHandle(subthreads[i]);
}
return 0;
}