I need to be able to stop a single worker thread from continuing to execute from arbitrary points in arbitrary other threads, including, but not limited to, the main thread. I had produced what I thought was working code last year, but investigations to-day following some thread deadlocks showed that it does not seem to work properly, especially as regards mutexes.
The code needs to run a particular method, path_explorer_t::step(), in a worker thread exactly once for every time that a helper method, start_path_explorer() is called in the main thread. start_path_explorer() is only ever called from the main thread.
Another method, stop_path_explorer() must be able to be called at any time by any thread (other than the thread that runs path_explorer_t::step()), and must not return until it is certain that path_explorer_t::step() has fully completed.
Additionally, path_explorer_t::step() must not be called if karte_t::world->is_terminating_threads() is true, but must instead terminate the thread at the next opportunity. The thread must not terminate in other circumstances.
The code that I have written to do this is as follows:
void* path_explorer_threaded(void* args) { karte_t* world = (karte_t*)args; path_explorer_t::allow_path_explorer_on_this_thread = true; karte_t::path_explorer_step_progress = 2; do { simthread_barrier_wait(&start_path_explorer_barrier); karte_t::path_explorer_step_progress = 0; simthread_barrier_wait(&start_path_explorer_barrier); pthread_mutex_lock(&path_explorer_mutex); if (karte_t::world->is_terminating_threads()) { karte_t::path_explorer_step_progress = 2; pthread_mutex_unlock(&path_explorer_mutex); break; } path_explorer_t::step(); karte_t::path_explorer_step_progress = 1; pthread_cond_signal(&path_explorer_conditional_end); karte_t::path_explorer_step_progress = 2; pthread_mutex_unlock(&path_explorer_mutex); } while (!karte_t::world->is_terminating_threads()); karte_t::path_explorer_step_progress = -1; pthread_exit(NULL); return args; } void karte_t::stop_path_explorer() { #ifdef MULTI_THREAD_PATH_EXPLORER pthread_mutex_lock(&path_explorer_mutex); if (path_explorer_step_progress = 0) { pthread_cond_wait(&path_explorer_conditional_end, &path_explorer_mutex); } pthread_mutex_unlock(&path_explorer_mutex); #endif } void karte_t::start_path_explorer() { #ifdef MULTI_THREAD_PATH_EXPLORER if (path_explorer_step_progress == -1) { // The threaded path explorer has been terminated, so do not wait // or else we will get a thread deadlock. return; } pthread_mutex_lock(&path_explorer_mutex); if (path_explorer_step_progress > 0) { simthread_barrier_wait(&start_path_explorer_barrier); } if(path_explorer_step_progress > -1) { simthread_barrier_wait(&start_path_explorer_barrier); } pthread_mutex_unlock(&path_explorer_mutex); #endif }
However, I find that, for reasons that I do not understand, the mutex lock in stop_path_explorer() does not work properly, and it does not prevent the mutex lock line from being passed in path_explorer_threaded, with the consequence that it is possible for the thread calling stop_path_explorer() to be waiting at the cond_wait and the worker thread itself to be waiting at the top barrier underneath "do". It also seems to be able to produce conditions in which the mutex can be unlocked twice, which gives rise to undefined behaviour unless I set it to recursive.
Do I just need to set the mutex attribute to recursive and add an extra unlock inside the conditional statement in stop_path_explorer(), or is a more fundamental redesign needed? If the latter, has anyone any suggestions as to how to go about it?
Thank you in advance for any help.
if (path_explorer_step_progress = 0)
should bewhile (path_explorer_step_progress == 0)
. (Notice there are two changes.) Also, waiting on a barrier while holding a mutex is suspicious. - David Schwartzpthread_cond_wait
in a loop because of spurious wakeups. - 1000ml