Skip to content

Instantly share code, notes, and snippets.

@midix
Last active January 24, 2024 09:41
Show Gist options
  • Save midix/33f6047380d20f2dbc76796e3b60a4f8 to your computer and use it in GitHub Desktop.
Save midix/33f6047380d20f2dbc76796e3b60a4f8 to your computer and use it in GitHub Desktop.
Demo for what happens if pthread_cond_signal is called before a thread goes into waiting mode with pthread_cond_wait
// See http://stackoverflow.com/questions/5536759/condition-variable-why-calling-pthread-cond-signal-before-calling-pthread-co for discussion
///////////////////////////////
// ThreadWaitCase.hpp
#include <pthread.h>
class ThreadWaitCase
{
public:
ThreadWaitCase();
virtual ~ThreadWaitCase();
void start();
void stop();
private:
static void* workerThreadFunc(void *context);
pthread_t m_workerThread;
pthread_cond_t m_workerWakeUpCall;
bool m_shouldStop;
// lock for m_wakeUpCall, m_shouldStop
pthread_mutex_t m_lock;
};
///////////////////////////////
// ThreadWaitCase.cpp
#include "ThreadWaitCase.hpp"
ThreadWaitCase::ThreadWaitCase() :
m_workerThread(NULL), m_shouldStop(false)
{
pthread_mutex_init(&m_lock, NULL);
pthread_cond_init(&m_workerWakeUpCall, NULL);
}
void ThreadWaitCase::ThreadWaitCase::start()
{
printf("Starting...\n");
m_shouldStop = false;
pthread_create(&m_workerThread, NULL,
&(ThreadWaitCase::workerThreadFunc), this);
printf("Started\n");
}
void* ThreadWaitCase::workerThreadFunc(void *context)
{
ThreadWaitCase *caseObj = (ThreadWaitCase*)context;
// mutex should be locked for pthread_cond_wait
int result = pthread_mutex_lock(&(caseObj->m_lock));
if (result != 0) {
printf("ERROR: failed to acquire lock!\n");
return NULL;
}
printf("Worker loop starts\n");
while(1) {
// <- the "loop get stuck on stop()" problem disappears if we change this to
// while(!caseObj->m_shouldStop) {
// to detect when the thread was asked to stop before we got to this point
// Explanation: pthread_cond_wait does not wake us up in that case,
// because even if m_workerWakeUpCall is always signaled right after m_shouldStop
// is set to true, still the condition will be missed if no threads are listening
// for it in pthread_cond_wait()
printf("Entering pthread_cond_wait, wake me up when needed...\n");
// pthread_cond_wait unlocks the mutex
int result = pthread_cond_wait(&(caseObj->m_workerWakeUpCall), &(caseObj->m_lock));
if (result != 0) {
printf("Cannot wait for worker wake up condition, error code: %d\n", result);
return NULL;
}
// mutex is again locked here by wait(); it's safe to work
printf("Loop received wake up call\n");
// spurious wakeups are possible,
// so check again if we should really do anything, and what exactly
if(caseObj->m_shouldStop) {
// should get here on stop(), but does not, because we set signal too early
printf("Loop received stop request\n");
break;
}
// will not get there in this particular test case
printf("Working, working, working...\n");
}// end of worker loop
// always unlock at exit
result = pthread_mutex_unlock(&(caseObj->m_lock));
if (result != 0) {
printf("ERROR: failed to release lock!\n");
return NULL;
}
printf("Loop quits\n");
return NULL;
}
void ThreadWaitCase::stop()
{
printf("Stopping...\n");
int result = pthread_mutex_lock(&m_lock);
if (result != 0) {
printf("ERROR: failed to acquire lock!\n");
return;
}
m_shouldStop = true;
result = pthread_cond_signal(&m_workerWakeUpCall);
if (result != 0) {
printf("ERROR: Condition signal not set!\n");
}
result = pthread_mutex_unlock(&m_lock);
if (result != 0) {
printf("ERROR: failed to release lock!\n");
}
printf("Entering join wait...\n");
// not locked here, wait to join
// it's ok to join even when the thread exited by itself without condition
result = pthread_join(m_workerThread, NULL);
if(result == ESRCH) {
// NOTICE: don't rely upon this ESRCH check in real life,
// use some bool flag to check if you have already joined the thread before
printf("Worker thread has been already joined before\n");
}
else
if (result != 0) {
printf("Cannot stop worker thread, join failed with error code: %d\n", result);
return;
}
printf("Thread joined\n");
}
ThreadWaitCase::~ThreadWaitCase()
{
printf("Destroying ThreadWaitCase...\n");
// it would be better if stop had been called outside before we get here,
// but we cannot always guarantee that, so we stop it here to be safe
stop();
pthread_mutex_destroy(&m_lock);
pthread_cond_destroy(&m_workerWakeUpCall);
}
///////////////////////////////
// main.cpp or some unit test function which does not exit the app immmediately at the end
// (else you won't know that the thread was killed without clean exit)
ThreadWaitCase twc;
twc.start();
// sleep(1); // <- of course, this also fixes the issue because we give a thread some
// time to enter pthread_cond_wait before stop() sets pthread_cond_signal
twc.stop(); // <- sometimes thread gets stuck spinning; condition not triggered
@debuti
Copy link

debuti commented Jan 24, 2024

This hardly compiles, and does not work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment