int tpInsertTask(ThreadPool *tp, void (*computeFunc)(void *), void *param) { int tpDestroyWasInvoked; if (sem_getvalue(&(tp->sem_tpDestroyWasInvoked), &tpDestroyWasInvoked)) {//ERROORRRREE return -1; } if (tpDestroyWasInvoked == 0) { return -1;//means that tpDestroy was invoked } if (pthread_mutex_lock(&(tp->mutex_taskQueue_lock))) {//ERROORRRREE return -1; } if (sem_getvalue(&(tp->sem_tpDestroyWasInvoked), &tpDestroyWasInvoked)) {//ERROORRRREE return -1; } if (tpDestroyWasInvoked == 0) { return -1;//means that tpDestroy was invoked } FuncAndParam *fap = malloc(sizeof(*fap)); if(!fap){ return -1; } fap->function = computeFunc; fap->param = param; osEnqueue(tp->taskQueue, (void *) fap); if (pthread_cond_signal(&(tp->cond_taskQueueNotEmpty))) {//ERROORRRREE return -1; } if (pthread_mutex_unlock(&(tp->mutex_taskQueue_lock))) {//ERROORRRREE return -1; } return 0; }
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); } }
/** * Add task. * * Basically: * 1. Make sure we're not being destroyed, and prevent destruction until we're done (dest_lock) * 2. Lock the task queue (unconditionally - no upper limit on number of tasks) * 3. Add a task * 4. Release the task queue lock and signal that the queue isn't empty (which one first?) * 5. Unlock the destruction lock (now we can kill the thread pool) */ int tpInsertTask(ThreadPool* tp, void (*func)(void *), void* param) { start_read(tp); // Read the state, and generally prevent it's change if (tp->state != ALIVE) { // If the thread pool is dying, end_read(tp); // release the state for reading (no more writing will be done) return -1; // and return indication of destruction. } // Try creating the task now, before locking the queue but after reading, because this // takes a long time so we don't want to prevent tasks from running. We're currently still // "reading" the thread pool state, but that's OK because a. it's a CREW lock so the threads // can read and see that the thread pool is a live and b. if a writer wants to write to the // state it's because the writer wants to destroy the thread pool - we want to prevent that // anyway at this point. Task* t = (Task*)malloc(sizeof(Task)); if (!t) { end_read(tp); return -1; } t->func = func; t->param = param; // Editing the task queue now. // There's no danger of deadlock with thread acquiring this lock: If a thread isn't waiting // for a signal then all it does is read the state (we can allow that here even if we're // waiting for the task_lock) or do it's thing (dequeue or exit). Either way, it'll let go // of the lock eventually. We don't need to give the "add" function priority because the // worst case scenario is that it'll take some time to enqueue the task... But that's only // if the threads are busy, so the task won't get done anyway. pthread_mutex_lock(&tp->task_lock); PRINT("Adding a task, lock is locked\n"); osEnqueue(tp->tasks,(void*)t); // Signal before releasing the lock - make a thread wait for the lock. pthread_cond_signal(&tp->queue_not_empty_or_dying); PRINT("Task added, signal given, lock is locked\n"); pthread_mutex_unlock(&tp->task_lock); PRINT("Task added, signal given, lock is unlocked\n"); // Allow destruction of the thread pool (allow writing to pool->state) end_read(tp); return 0; }