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); } }
void osDestroyQueue(OSQueue* q) { if(q == NULL) return; while(osDequeue(q) != NULL); free(q); }
void* getAndExecuteTasksForever(void *voidPtrTP) { ThreadPool *tp = (ThreadPool *) voidPtrTP; while (1) { if (pthread_mutex_lock(&(tp->mutex_taskQueue_lock))) {//ERROORRRREE return NULL; } while (osIsQueueEmpty(tp->taskQueue) || tp->tpDestroyNeedsLock==1) { if (pthread_cond_wait(&(tp->cond_taskQueueNotEmpty), &(tp->mutex_taskQueue_lock))) {//ERROORRRREE return NULL; } } void *FAPAddress = (osDequeue(tp->taskQueue)); if (pthread_mutex_unlock(&(tp->mutex_taskQueue_lock))) {//ERROORRRREE return NULL; } FuncAndParam FAP = *((FuncAndParam*)FAPAddress); free(FAPAddress); FAP.function(FAP.param); } }
/** * The function passed to created threads. * * NOTE: is_empty(queue) is called a lot: It should be noted that we * must make sure it's only called when we have the queue lock! * * 1. Lock the task queue. We're using a condition lock, so we'll * give up the lock until there is a task to run OR the tpDestroy * function sent a broadcast to all threads that they should clean * up. * 2. Wait for the signal (task inserted, or tp being destroyed). * 3. Now that the queue is locked, check the destruction state. This * state should be valid because a. the change from ALIVE to * something else is a one-way change, b. even if the following * happened: * - Task added * - Thread got out of the WHILE loop * - CONTEXT SWITCH * - Main thread (pool creator) called tp_destroy, state changed * - CONTEXT SWITCH * - Back to our thread, got to the switch() statement and found * out we're dying * This is the desired behaviour (Piazza @281) - we do not need to * make sure tasks added before calls to tpDestroy will be executed * if tpDestroy is called in DO_RUN mode, even if all threads were * available when the task was added. * 4. If we're ALIVE, that means pool->queue IS NOT EMPTY (otherwise we * would still be in the while loop, because you can't change DO_RUN * or DO_ALL back to ALIVE so there's no danger we left the while() * loop because of state!=ALIVE but got to state==ALIVE in the * switch), so we can just dequeue a task and run it (remember to * unlock before running!). * 5. If we're DO_ALL, it's like ALIVE but first check if there's * something to run (unlike the ALIVE state, we don't know for sure). * If there is, run it; otherwise, exit (no more tasks will come). * 6. If we're DO_RUN, exit. Don't take another task, leave them to rot. * 7. Rinse and repeat */ void* thread_func(void* void_tp) { int pid = TID(); // Some useful variables State state; Task* t; ThreadPool* tp = (ThreadPool*)void_tp; #if HW3_DEBUG // Initialize tp->tids pthread_t self = pthread_self(); int thread_i; for (thread_i=0; thread_i<tp->N; ++thread_i) if (pthread_equal(tp->threads[thread_i],self)) { tp->tids[thread_i]=pid; break; } #endif PRINT("Thread %d started it's function\n",pid); // Main thread task while(1) { // Get the initial state and the task lock, when we need it (task to do or we're dying) // IMPORTANT: LOCK THE TASK LOCK BEFORE READING THE STATE! // Otherwise, we can see this situation: // - T1 reads the state, it's ALIVE // - CS-->main thread // - Main thread calls tpDestroy // - Main thread broadcasts, starts waiting for all threads // - CS-->T1 // - T1 locks the task lock (remember: state==ALIVE) // - The task queue is empty and state==ALIVE so T1 will wait for a signal that will never come. // Hence, DO NOT do this: // 1. state = read_state(tp); // 2. pthread_mutex_lock(&tp->task_lock); // But do it the other way round: pthread_mutex_lock(&tp->task_lock); // This is OK because during INIT, we don't lock the task queue (after its creation) state = read_state(tp); PRINT("Thread %d locked the task queue\n",pid); while (osIsQueueEmpty(tp->tasks) && state == ALIVE) { // Wait for a task OR the destruction of the pool PRINT("Thread %d started waiting for a signal\n",pid); pthread_cond_wait(&tp->queue_not_empty_or_dying,&tp->task_lock); // Either one gives a signal state = read_state(tp); PRINT("Thread %d got the signal and locked the lock\n",pid); } PRINT("Thread %d got out of the while() loop, state==%s\n",pid,state_string(read_state(tp))); switch(state) { case ALIVE: // If we're not dying, take a task and do it. t = (Task*)osDequeue(tp->tasks); pthread_mutex_unlock(&tp->task_lock); PRINT("Thread %d doing it's task\n",pid); t->func(t->param); free(t); break; case DO_ALL: // If we're dying, but we should clean up the queue: if (!osIsQueueEmpty(tp->tasks)) { // THIS TEST IS NOT USELESS! We may have got here t = (Task*)osDequeue(tp->tasks); // via a broadcast() call from tp_destroy and the pthread_mutex_unlock(&tp->task_lock); // state may be DO_ALL but is_empty() may be true... PRINT("Thread %d doing it's task\n",pid); // Thus, the while() loop terminated and we got here. t->func(t->param); free(t); } else { // If we're here, there are no more tasks to dequeue! pthread_mutex_unlock(&tp->task_lock); // As we're being destroyed anyway, exit. PRINT("Thread %d unlocked the lock and returning\n",pid); return NULL; } break; case DO_RUN: // If we're dying and no more tasks should be done, pthread_mutex_unlock(&tp->task_lock); // just exit before dequeuing anything... PRINT("Thread %d unlocked the lock and returning\n",pid); return NULL; break; } } }
/** * 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; }