这个线程池和通常用的有些不一样,通常,都应该具有任务队列。主线程把任务放入任务队列就完了,这个线程池的话,没有任务队列,有就是说,
当最大的线程数量在工作的时候,主线程会挂起。
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define MAX_THREAD 10
#define MIN_THREAD 1
typedef void (*threadJobFunc)(void *jobdata);
typedef struct _threadPool threadPool;
struct _threadPool {
int quit;
int have_work; /* we add this arguement, to avoid workers's spurious wakeups. */
int can_assign; /* if a job is assigning, this will be zero. */
pthread_t tids[MAX_THREAD];
size_t max_workers;
size_t min_workers;
size_t free_workers; /* worker who is free(sleep). */
size_t nworkers; /* now all workers' nunber. */
pthread_mutex_t mutex; /* control the data above. */
threadJobFunc job_func; /* function you want to call. */
void * job_data; /* it depends on you, maybe a struct or list and so on. */
pthread_cond_t job_assign_cond; /* ensure job assigning after former job have been receive colectly, as we don't use job queue.*/
pthread_cond_t worker_cond; /* worker's sleep or wake up. */
pthread_cond_t manger_cond; /* manger's sleep or wakeup. */
};
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int cakes = 0;
static threadPool thread_pool;
/*
release the mtx, when the thread is canceled, terminates by calling
pthread_exit(3) or a thread calls pthread_cleanup_pop().
*/
static void cleanup_handler(void *arg)
{
printf("Cleanup handler of second thread./n");
(void)pthread_mutex_unlock(&mtx);
}
static void func_create(void * arg)
{
time_t ntime, end_time;
pthread_cleanup_push(cleanup_handler, NULL);
ntime = time(NULL);
end_time = ntime + 5;
while(ntime < end_time) /* do create */
{
ntime = time(NULL);
}
pthread_mutex_lock(&mtx);
cakes += *((int*)arg);
pthread_mutex_unlock(&mtx);
printf("create %d\n", *((int*)arg));
free(arg);
pthread_cleanup_pop(0);
return;
}
static void func_eat(void * arg)
{
time_t ntime = time(NULL);
time_t end_time = ntime + 5;
/* do eat. */
while (ntime < end_time) {
ntime = time(NULL);
}
pthread_cleanup_push(cleanup_handler, NULL);
/*
? PROBLEM:
here may cause eat success, but found cakes equal to 0.
if decrease cakes first, may cause eat fail, but create more cakes than
max cake number.
*/
pthread_mutex_lock(&mtx);
cakes -= *((int*)arg);
printf("eate %d\n", *((int*)arg));
pthread_mutex_unlock(&mtx);
free(arg);
pthread_cleanup_pop(0);
return;
}
/*
Here worker is a thread, it do the job, when we have, if not it sleep.
*/
static void * worker(void * arg)
{
threadJobFunc work_func;
void * work_data;
while(1) {
pthread_mutex_lock(&(thread_pool.mutex));
while(!thread_pool.have_work && !thread_pool.quit) {
pthread_cond_wait(&(thread_pool.worker_cond), &(thread_pool.mutex));
}
thread_pool.have_work = 0; /* current work have been taken. */
pthread_mutex_unlock(&(thread_pool.mutex));
if (thread_pool.quit) break; /* here stop the thread. */
/* get the job data and function received and signal the job_assigned_cond. */
work_func = thread_pool.job_func;
work_data = thread_pool.job_data;
pthread_mutex_lock(&(thread_pool.mutex));
thread_pool.can_assign = 1;
pthread_mutex_unlock(&(thread_pool.mutex));
pthread_cond_signal(&(thread_pool.job_assign_cond));
printf("job func called %p\n",thread_pool.job_data);
work_func(work_data); /* doing the job. */
pthread_mutex_lock(&(thread_pool.mutex));
thread_pool.free_workers++;
pthread_mutex_unlock(&(thread_pool.mutex));
pthread_cond_signal(&thread_pool.manger_cond); /* send signal to the manger the worker is free. */
printf("signal manger cond\n");
}
return;
}
/*
Here is the mutiple threads' entrance.
*/
void manger(void)
{
int s;
while(1) {
if (thread_pool.free_workers > 0) { /* Here read, we asume we don't need to lock. */
/* This part's order can not be change. */
pthread_mutex_lock(&(thread_pool.mutex));
thread_pool.have_work = 1;
thread_pool.free_workers--;
pthread_mutex_unlock(&(thread_pool.mutex));
pthread_cond_signal(&thread_pool.worker_cond);
printf("signal worker_cond\n");
break;
}
/*
Here we don't lock as there is only one manager, so we will not read the nworkers and create thread in
the same time. If there is more manger this must be locked, as threr maybe two or more threads to read
nworkers(<max_workers), but after each threads creation, the nworkers may biger than max_workers.
*/
if (thread_pool.nworkers < thread_pool.max_workers) {
s = pthread_create(&(thread_pool.tids[thread_pool.nworkers]), NULL, worker, NULL);
if ( s!= 0) {
printf("[%s:%d:%s]: %s\n", __FILE__, __LINE__, __FUNCTION__,
"Thread create error!\n");
continue;
} else {
printf("create new worker\n");
}
pthread_mutex_lock(&(thread_pool.mutex));
thread_pool.nworkers++;
thread_pool.have_work = 1;
pthread_mutex_unlock(&(thread_pool.mutex));
pthread_cond_signal(&thread_pool.worker_cond);
printf("signal worker_cond\n");
break;
}
/*
Here exctly we don't have to lock the mutex, as olny one manger, and only manger will wait the manger_cond.
but it seems we have to. Ok, it may ensure there will no two threads wait the thread_pool. manger_cond in
the same time.
*/
pthread_mutex_lock(&(thread_pool.mutex));
pthread_cond_wait(&(thread_pool.manger_cond), &(thread_pool.mutex));
pthread_mutex_unlock(&(thread_pool.mutex));
}
return;
}
int init_pool(void)
{
int i, s;
pthread_cond_init(&(thread_pool.worker_cond),NULL);
pthread_cond_init(&(thread_pool.manger_cond),NULL);
pthread_cond_init(&(thread_pool.job_assign_cond),NULL);
pthread_mutex_init(&(thread_pool.mutex),NULL);
thread_pool.quit = 0;
thread_pool.can_assign = 1;
thread_pool.have_work = 0;
thread_pool.max_workers = MAX_THREAD;
thread_pool.min_workers = MIN_THREAD;
for (i=0; i<thread_pool.min_workers; i++) {
s = pthread_create(&(thread_pool.tids[i]), NULL, worker, NULL);
if (s != 0) {
printf("[%s:%d:%s]: %s\n", __FILE__, __LINE__, __FUNCTION__,
"Thread create error!\n");
return -1;
} else {
printf("create new worker\n");
}
}
thread_pool.free_workers = thread_pool.min_workers;
thread_pool.nworkers = thread_pool.min_workers;
return 0;
}
void destory_pool(void)
{
/*TODO wait other thread to join. */
int i = 0;
thread_pool.quit = 1; /* only the main thread wil change the arguement qiut, so we don't lock. */
pthread_cond_broadcast(&(thread_pool.worker_cond));
pthread_cond_broadcast(&(thread_pool.job_assign_cond));
for (i=0; i<thread_pool.nworkers; i++)
pthread_join(thread_pool.tids[i], NULL);
pthread_cond_destroy(&(thread_pool.worker_cond));
pthread_cond_destroy(&(thread_pool.manger_cond));
pthread_cond_destroy(&(thread_pool.job_assign_cond));
pthread_mutex_destroy(&(thread_pool.mutex));
}
int main(void)
{
int * data;
time_t ntime, end_time;
char qf[10];
int num = 0;
int num_eat = 0;
if ( init_pool() ) goto clean;
/* main thread are working. */
ntime = time(NULL);
end_time = ntime + 5;
while(1)
{
ntime = time(NULL);
if (ntime > end_time) {
ntime = time(NULL);
end_time = ntime + 50;
printf("exit(y/n)?");
scanf("%s", qf);
if (qf[0] == 'y' || qf[0] == 'Y') {
break;
}
}
/*
we must ensure job assigning after the former job have been receive colectly.
we will release the lock, when the job receive by a thread.
*/
data = (int *)malloc(sizeof(int));
*data = 2;
num += *data;
pthread_mutex_lock(&(thread_pool.mutex));
while(!thread_pool.can_assign) {
pthread_cond_wait(&(thread_pool.job_assign_cond), &(thread_pool.mutex));
}
thread_pool.can_assign = 0;
pthread_mutex_unlock(&(thread_pool.mutex));
printf("main call create cakes\n");
thread_pool.job_func = func_create;
thread_pool.job_data = data;
manger();
data = (int *)malloc(sizeof(int));
*data = 2;
num_eat += *data;
pthread_mutex_lock(&(thread_pool.mutex));
while(!thread_pool.can_assign) {
pthread_cond_wait(&(thread_pool.job_assign_cond), &(thread_pool.mutex));
}
thread_pool.can_assign = 0;
pthread_mutex_unlock(&(thread_pool.mutex));
printf("main call eat cakes\n");
thread_pool.job_func = func_eat;
thread_pool.job_data = data;
manger();
}
clean:
destory_pool();
printf("create all: %d\teat all: %d\t left: %d\n", num, num_eat, cakes);
return 0;
}