/** * threadpool.c * * This file will contain your implementation of a threadpool. */ #include #include #include #include #include #include #include "threadpool.h" #include "list.c" //#define DEBUG /*---------------------------------------------------------------------- Utility function: "elapsed-time fprintf". fprintf, but prepends the time difference between the first argument and now. */ void etfprintf(struct timeval then, ...) { va_list args; struct timeval now; struct timeval diff; FILE *out; char *fmt; va_start(args, then); out = va_arg(args, FILE *); fmt = va_arg(args, char *); gettimeofday(&now, NULL); timersub(&now, &then, &diff); fprintf(out, "%03d.%06d:", diff.tv_sec, diff.tv_usec); vfprintf(out, fmt, args); fflush(NULL); va_end(args); } typedef enum { ALL_RUN, ALL_EXIT } poolstate_t; /*---------------------------------------------------------------------- _threadpool is the internal threadpool structure that is cast to type "threadpool" before it given out to callers. All accesses to a _threadpool (both read and write) must be protected by that pool's mutex. Worker threads wait on a "job_posted" signal; when one is received the receiving thread (which has the mutex): * checks the pool state. If it is ALL_EXIT the thread decrements the pool's 'live' count, yields the mutex and exits; otherwise continues: * while the job queue is not empty: * copy the job and arg fields into the thread, * NULL out the pool's job and arg fields * signal "job_taken" * yield the mutex and run the job on the arg. * grab the mutex * when a null job field is found, wait on "job_posted" The dispatcher thread dispatches a job as follows: * grab the pool's mutex * while the job field is not NULL: * signal "job_posted" * wait on "job_taken" * copy job and args to pool, signal "job_posted", yield the mutex */ typedef struct _threadpool_st { struct timeval created; // When the threadpool was created. pthread_t *array; // The threads themselves. pthread_mutex_t mutex; // protects all vars declared below. pthread_cond_t job_posted; // dispatcher: "Hey guys, there's a job!" pthread_cond_t job_taken; // a worker: "Got it!" poolstate_t state; // Threads check this before getting job. int arrsz; // Number of entries in array. int live; // Number of live threads in pool (when // pool is being destroyed, live<=arrsz) queueHead *theQueue; // queue of work orders } _threadpool; /*---------------------------------------------------------------------- Define the life of a working thread. */ void * do_work(void * owning_pool) { // Convert pointer to owning pool to proper type. _threadpool *pool = (_threadpool *) owning_pool; // Remember my creation sequence number int myid = pool->live; // When we get a posted job, we copy it into these local vars. dispatch_fn myjob; void *myarg; dispatch_fn mycleaner; void *mycleanarg; #ifdef DEBUG etfprintf(pool->created, stderr, " >>> Thread[%d] starting, grabbing mutex.\n", myid); #endif pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); pthread_cleanup_push(pthread_mutex_unlock, (void *) &pool->mutex); // Grab mutex so we can begin waiting for a job if (0 != pthread_mutex_lock(&pool->mutex)) { perror("\nMutex lock failed!:"); exit(EXIT_FAILURE); } // Main loop: wait for job posting, do job(s) ... forever for( ; ; ) { #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Thread[%d] waiting for signal.\n", myid); #endif while(isJobAvailable(pool->theQueue)==0) { pthread_cond_wait(&pool->job_posted, &pool->mutex); } #ifdef DEBUG etfprintf(pool->created, stderr, " >>> Thread[%d] received signal.\n", myid); #endif // We've just woken up and we have the mutex. Check pool's state if (ALL_EXIT == pool->state) break; // while we find work to do getWorkOrder(pool->theQueue, &myjob, &myarg, &mycleaner, &mycleanarg); pthread_cond_signal(&pool->job_taken); #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Thread[%d] yielding mutex, taking job.\n", myid); #endif // Yield mutex so other jobs can be posted if (0 != pthread_mutex_unlock(&pool->mutex)) { perror("\n\nMutex unlock failed!:"); exit(EXIT_FAILURE); } // Run the job we've taken if(mycleaner != NULL) { pthread_cleanup_push(mycleaner,mycleanarg); myjob(myarg); pthread_cleanup_pop(1); } else { myjob(myarg); } #ifdef DEBUG etfprintf(pool->created, stderr, " >>> Thread[%d] job done, grabbing mutex.\n", myid); #endif // Grab mutex so we can grab posted job, or (if no job is posted) // begin waiting for next posting. if (0 != pthread_mutex_lock(&pool->mutex)) { perror("\n\nMutex lock failed!:"); exit(EXIT_FAILURE); } } // If we get here, we broke from loop because state is ALL_EXIT. --pool->live; #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Thread[%d] exiting (signalling 'job_taken').\n", myid); #endif // We're not really taking a job ... but this signals the destroyer // that one thread has exited, so it can keep on destroying. pthread_cond_signal(&pool->job_taken); if (0 != pthread_mutex_unlock(&pool->mutex)) { perror("\n\nMutex unlock failed!:"); exit(EXIT_FAILURE); } pthread_cleanup_pop(1); return NULL; } /*---------------------------------------------------------------------- Create a thread pool. */ threadpool create_threadpool(int num_threads_in_pool) { _threadpool *pool; // pool we create and hand back int i; // work var // sanity check the argument if ((num_threads_in_pool <= 0) || (num_threads_in_pool > MAXT_IN_POOL)) return NULL; // create the _threadpool struct pool = (_threadpool *) malloc(sizeof(_threadpool)); if (pool == NULL) { fprintf(stderr, "\n\nOut of memory creating a new threadpool!\n"); return NULL; } // initialize everything but the array and live thread count pthread_mutex_init(&(pool->mutex), NULL); pthread_cond_init(&(pool->job_posted), NULL); pthread_cond_init(&(pool->job_taken), NULL); pool->arrsz = num_threads_in_pool; pool->state = ALL_RUN; pool->theQueue = makeQueue(num_threads_in_pool); gettimeofday(&pool->created, NULL); // create the array of threads within the pool pool->array = (pthread_t *) malloc (pool->arrsz * sizeof(pthread_t)); if (NULL == pool->array) { fprintf(stderr, "\n\nOut of memory allocating thread array!\n"); free(pool); pool = NULL; return NULL; } // bring each thread to life (update counters in loop so threads can // access pool->live to find out their ID# for (i = 0; i < pool->arrsz; ++i) { if (0 != pthread_create(pool->array + i, NULL, do_work, (void *) pool)) { perror("\n\nThread creation failed:"); exit(EXIT_FAILURE); } pool->live = i+1; pthread_detach(pool->array[i]); // automatic cleanup when thread exits. } #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Threadpool created with %d threads.\n", num_threads_in_pool); #endif return (threadpool) pool; } /*---------------------------------------------------------------------- Dispatch a thread */ void dispatch(threadpool from_me, dispatch_fn dispatch_to_here, void *arg) { _threadpool *pool = (_threadpool *) from_me; int old_cancel; if(pool == (_threadpool *) arg) { } else { pthread_cleanup_push(pthread_mutex_unlock, (void *) &pool->mutex); #ifdef DEBUG etfprintf(pool->created, stderr, " >>> Dispatcher: grabbing mutex.\n"); #endif // Grab the mutex if (0 != pthread_mutex_lock(&pool->mutex)) { perror("Mutex lock failed (!!):"); exit(-1); } while(!canAcceptWork(pool->theQueue)) { pthread_cond_signal(&pool->job_posted); pthread_cond_wait(&pool->job_taken,&pool->mutex); } #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Dispatcher: job queue full, %s\n", "signaling 'posted', waiting on 'taken'."); #endif // Finally, there's room to post a job. Do so and signal workers. #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Dispatcher: posting job, signaling 'posted', yielding mutex\n"); #endif addWorkOrder(pool->theQueue,dispatch_to_here,arg,NULL,NULL); pthread_cond_signal(&pool->job_posted); // Yield mutex so a worker can pick up the job if (0 != pthread_mutex_unlock(&pool->mutex)) { perror("\n\nMutex unlock failed!:"); exit(EXIT_FAILURE); } pthread_cleanup_pop(1); } } void dispatch_with_cleanup(threadpool from_me, dispatch_fn dispatch_to_here, void *arg, dispatch_fn cleaner_func, void * cleaner_arg) { _threadpool *pool = (_threadpool *) from_me; int old_cancel; if(pool == (_threadpool *) arg) { } else { pthread_cleanup_push(pthread_mutex_unlock, (void *) &pool->mutex); #ifdef DEBUG etfprintf(pool->created, stderr, " >>> Dispatcher: grabbing mutex.\n"); #endif // Grab the mutex if (0 != pthread_mutex_lock(&pool->mutex)) { perror("Mutex lock failed (!!):"); exit(-1); } while(!canAcceptWork(pool->theQueue)) { pthread_cond_signal(&pool->job_posted); pthread_cond_wait(&pool->job_taken,&pool->mutex); } #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Dispatcher: job queue full, %s\n", "signaling 'posted', waiting on 'taken'."); #endif // Finally, there's room to post a job. Do so and signal workers. #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Dispatcher: posting job, signaling 'posted', yielding mutex\n"); #endif addWorkOrder(pool->theQueue,dispatch_to_here,arg,cleaner_func,cleaner_arg); pthread_cond_signal(&pool->job_posted); // Yield mutex so a worker can pick up the job if (0 != pthread_mutex_unlock(&pool->mutex)) { perror("\n\nMutex unlock failed!:"); exit(EXIT_FAILURE); } pthread_cleanup_pop(1); } } /*---------------------------------------------------------------------- Destroy a thread pool. If there is a job still waiting for a thread to execute it, tough. We set the ALL_EXIT flag anyways. */ void destroy_threadpool(threadpool destroyme) { _threadpool *pool = (_threadpool *) destroyme; int oldtype; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); pthread_cleanup_push(pthread_mutex_unlock, (void *) &pool->mutex); // Cause all threads to exit. Because they were detached when created, // the underlying memory for each is automatically reclaimed. #ifdef DEBUG etfprintf(pool->created, stderr, " >>> Destroyer: grabbing mutex, setting state to ALL_EXIT. Live = %d\n", pool->live); #endif // Grab the mutex if (0 != pthread_mutex_lock(&pool->mutex)) { perror("Mutex lock failed (!!):"); exit(-1); } pool->state = ALL_EXIT; while (pool->live > 0) { #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Destroyer: signalling 'job_posted', waiting on 'job_taken'.\n"); #endif // get workers to check in ... pthread_cond_signal(&pool->job_posted); // ... and wake up when they check out. pthread_cond_wait(&pool->job_taken, &pool->mutex); #ifdef DEBUG etfprintf(pool->created, stderr, " >>> Destroyer: received 'job_taken'. Live = %d\n", pool->live); #endif } // Null-out entries in pool's thread array; free array. memset(pool->array, 0, pool->arrsz * sizeof(pthread_t)); free(pool->array); // Destroy the mutex and condition variables in the pool. #ifdef DEBUG etfprintf(pool->created, stderr, " <<< Destroyer: releasing mutex prior to destroying it.\n"); #endif pthread_cleanup_pop(0); if (0 != pthread_mutex_unlock(&pool->mutex)) { perror("\n\nMutex unlock failed!:"); exit(EXIT_FAILURE); } #ifdef DEBUG etfprintf(pool->created, stderr, " --- Destroyer: destroying mutex.\n"); #endif if (0 != pthread_mutex_destroy(&pool->mutex)) { perror("\nMutex destruction failed!:"); exit(EXIT_FAILURE); } #ifdef DEBUG etfprintf(pool->created, stderr, " --- Destroyer: destroying conditional variables.\n"); #endif if (0 != pthread_cond_destroy(&pool->job_posted)) { perror("\nCondition Variable 'job_posted' destruction failed!:"); exit(EXIT_FAILURE); } if (0 != pthread_cond_destroy(&pool->job_taken)) { perror("\nCondition Variable 'job_taken' destruction failed!:"); exit(EXIT_FAILURE); } // Zero out all bytes of the pool memset(pool, 0, sizeof(_threadpool)); // Free the pool and null out the pointer to it free(pool); pool = NULL; destroyme = NULL; } void destroy_threadpool_immediately(threadpool destroymenow) { _threadpool *pool = (_threadpool *) destroymenow; int oldtype,i; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); pthread_cleanup_push(pthread_mutex_unlock, (void *) &pool->mutex); pthread_mutex_lock(&pool->mutex); for(i=0;iarrsz;i++) { if(0 != pthread_cancel(pool->array[i])) { perror("Destruction of thread failed!\n"); } } #ifdef DEBUG etfprintf(pool->created, stderr, " --- Destroyer: destroying mutex.\n"); #endif pthread_cleanup_pop(0); if (0 != pthread_mutex_destroy(&pool->mutex)) { perror("\nMutex destruction failed!:"); exit(EXIT_FAILURE); } #ifdef DEBUG etfprintf(pool->created, stderr, " --- Destroyer: destroying conditional variables.\n"); #endif if (0 != pthread_cond_destroy(&pool->job_posted)) { perror("\nCondition Variable 'job_posted' destruction failed!:"); exit(EXIT_FAILURE); } if (0 != pthread_cond_destroy(&pool->job_taken)) { perror("\nCondition Variable 'job_taken' destruction failed!:"); exit(EXIT_FAILURE); } // Zero out all bytes of the pool memset(pool, 0, sizeof(_threadpool)); // Free the pool and null out the pointer to it free(pool); pool = NULL; destroymenow = NULL; }