diff --git a/src/threadpool.c b/src/threadpool.c
index 79bd586cd9d32b22a21f05ca66bfcc6b30bdfbbf..d1b3d0c4269a17022e30c5f5f8da5c8654b79034 100644
--- a/src/threadpool.c
+++ b/src/threadpool.c
@@ -174,6 +174,10 @@ void *threadpool_runner(void *data) {
     /* Wait for the controller. */
     pthread_barrier_wait(&tp->run_barrier);
 
+    /* If no map function is specified, just die. We use this as a mechanism
+       to shut down threads without leaving the barriers in an invalid state. */
+    if (tp->map_function == NULL) pthread_exit(NULL);
+
     /* Do actual work. */
     threadpool_chomp(tp, atomic_inc(&tp->num_threads_running));
   }
@@ -310,6 +314,22 @@ void threadpool_reset_log(struct threadpool *tp) {
  * @brief Frees up the memory allocated for this #threadpool.
  */
 void threadpool_clean(struct threadpool *tp) {
+  /* Destroy the runner threads by calling them with a NULL mapper function
+     and waiting for all the threads to terminate. This ensures that no thread
+     is still waiting at a barrier. */
+  tp->map_function = NULL;
+  pthread_barrier_wait(&tp->run_barrier);
+  for (int k = 0; k < tp->num_threads - 1; k++) {
+    void *retval;
+    pthread_join(tp->threads[k], &retval);
+  }
+
+  /* Release the barriers. */
+  if (pthread_barrier_destroy(&tp->wait_barrier) != 0 ||
+      pthread_barrier_destroy(&tp->run_barrier) != 0)
+    error("Failed to destory threadpool barriers.");
+
+  /* Clean up memory. */
   free(tp->threads);
 #ifdef SWIFT_DEBUG_THREADPOOL
   for (int k = 0; k < tp->num_threads; k++) {