void tpDestroy(ThreadPool *tp, int shouldWaitForTasks) { if(sem_trywait(&(tp->sem_tpDestroyWasInvoked))){//we are already during destruction return; } tp->tpDestroyNeedsLock = 1; if (pthread_mutex_lock(&(tp->mutex_taskQueue_lock))) {//ERROORRRREE return;//TODO think about this } if(shouldWaitForTasks==0){ while(!(osIsQueueEmpty(tp->taskQueue))){ free(osDequeue(tp->taskQueue)); } } int i; for(i = 0 ; i < tp->numOfThreads ; ++i){ FuncAndParam* fap = malloc(sizeof(*fap)); fap->function = selfDestruct; fap->param = NULL; osEnqueue(tp->taskQueue, (void*)fap); } tp->tpDestroyNeedsLock = 0; if (pthread_cond_broadcast(&(tp->cond_taskQueueNotEmpty))) {//ERROORRRREE return; } if (pthread_mutex_unlock(&(tp->mutex_taskQueue_lock))) {//ERROORRRREE return; } int elDestroyador = getpid();//id of the process that invoked tpDestroy int flagCurrentIsThread = 0;//true iff the process that invoked tpDestroy is one of the threads for(i = 0 ; i < tp->numOfThreads ; ++i){ if((tp->threadIdArray)[i]==elDestroyador){ flagCurrentIsThread = 1; }else{ pthread_join((tp->threadIdArray)[i], NULL); } } while(!(osIsQueueEmpty(tp->taskQueue))){ free(osDequeue(tp->taskQueue)); } osDestroyQueue(tp->taskQueue); if (pthread_cond_destroy(&(tp->cond_taskQueueNotEmpty))) {//ERROORRRREE return; } if (pthread_mutex_destroy(&(tp->mutex_taskQueue_lock))) {//ERROORRRREE return; } if (sem_destroy(&(tp->sem_tpDestroyWasInvoked))) {//ERROORRRREE return; } free(tp->threadIdArray); free(tp); if(flagCurrentIsThread){//if the process that invoked tpDestroy is one of the threads pthread_exit(NULL); } }
/** * Destroys the thread pool. * * 1. Lock the destruction lock (top priority... don't want to starve here) * 2. Change the state * 3. Unlock the destruction lock (now, no more tasks can be added because the state * is checked first, and the threads should know to check the state before dequeuing * anything) * 4. Lock the tasks lock, so we're sure all threads are waiting for a signal (or for * the lock) to prevent deadlock (see comments bellow). * 5. Broadcast to all threads waiting for tasks: if there's nothing now, there never * will be! * 6. Unlock the tasks lock (to allow the threads to do their thing) * 7. Wait for all threads to finish * 8. Destroy all fields of the pool */ void tpDestroy(ThreadPool* tp, int should_wait_for_tasks) { if (!tp) // Sanity check return; if (!start_write(tp)) { // Someone is already writing to pool->state! return; // This should only happen ONCE... } if (tp->state != ALIVE) { // Destruction already in progress. end_write(tp); // This can happen if destroy() is called twice fast return; } tp->state = should_wait_for_tasks ? DO_ALL : DO_RUN; // Enter destroy mode PRINT("Destroying the pool! Now allowing state read.\n"); end_write(tp); // Allow reading the state // Make sure the queue isn't busy. // We shouldn't encounter deadlock/livelock here because threads wait for signals, and the only // possible source of a signal is here or in tpInsertTask(). // If we lock this only AFTER we change the/ destruction state, we can create a situation where // a thread waits forever for a signal that will never come: // - A thread is in it's while loop, the queue is // empty and the pool isn't being destroyed // so it enters the body of the loop. Before it // starts waiting for a signal... // - CS-->Main thread // - Main thread calls destroy, doesn't wait for // the task_lock and writes the new destruction // state. Then, broadcasts a signal to listening // threads. // - CS-->Previous thread // - Starts waiting for a signal that will never // come // By locking here before broadcasting the destruction, we make sure all threads are waiting for // the lock or for the signal! Either way, after the signal, the threads will know what to do and // exit the loop. pthread_mutex_lock(&tp->task_lock); PRINT("Pool destroyed, task lock locked, sending broadcast...\n"); pthread_cond_broadcast(&tp->queue_not_empty_or_dying); PRINT("... done. Unlocking the task lock.\n"); pthread_mutex_unlock(&tp->task_lock); // Wait for all threads to exit (this could take a while) int i; for (i=0; i<tp->N; ++i) { #if HW3_DEBUG PRINT("Waiting for T%2d to finish (tid=%d)...\n", i+1, tp->tids[i]); #else PRINT("Waiting for thread %d to finish...\n", i+1); #endif pthread_join(tp->threads[i], NULL); #if HW3_DEBUG PRINT("T%2d (tid=%d) done.\n", i+1, tp->tids[i]); #else PRINT("Thread %d done.\n", i+1); #endif } PRINT("All threads done! Locking the task lock and destroying the task queue...\n"); // Cleanup! // Tasks (we can still lock here): pthread_mutex_lock(&tp->task_lock); while (!osIsQueueEmpty(tp->tasks)) { Task* t = (Task*)osDequeue(tp->tasks); free(t); } osDestroyQueue(tp->tasks); PRINT("Done. Unlocking the task queue\n"); pthread_mutex_unlock(&tp->task_lock); // Locks: PRINT("Doing thread pool cleanup...\n"); pthread_mutex_destroy(&tp->task_lock); pthread_mutexattr_destroy(&tp->mutex_type); sem_destroy(&tp->r_num_mutex); sem_destroy(&tp->w_flag_mutex); sem_destroy(&tp->r_entry); sem_destroy(&tp->r_try); sem_destroy(&tp->state_lock); pthread_cond_destroy(&tp->queue_not_empty_or_dying); // Last cleanup, and out: #if HW3_DEBUG free(tp->tids); #endif free(tp->threads); free(tp); PRINT("Done destroying thread pool.\n"); return; }