/* thread_pool.h */
#ifndef THREAD_POOL
#define THREAD_POOL
#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 void * worker(void * arg);
void manger(threadPool * thread_pool);
int init_pool(threadPool * thread_pool);
void destory_pool(threadPool * thread_pool);
#endif
/* thread_pool.c */
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include "thread_pool.h"
/*
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;
threadPool * thread_pool = (threadPool *)arg;
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(threadPool * thread_pool)
{
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, thread_pool);
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;
}
/*
return 0 success. other fail.
*/
int init_pool(threadPool * thread_pool)
{
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, thread_pool);
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(threadPool * thread_pool)
{
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));
}
/*
if success return 0, else return -1.
!!!attention: data must release by the job_func!
*/
int assign_job(threadPool * thread_pool, threadJobFunc job_func, void * data) {
if (job_func == NULL) return -1;
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 = job_func;
thread_pool->job_data = data;
manger(thread_pool);
return 0;
}
/* test.c */
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include "thread_pool.h"
static pthread_mutex_t mtx = PTHREAD_MUTEX_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;
}
int main(void)
{
int * data;
time_t ntime, end_time;
char qf[10];
int num = 0;
int num_eat = 0;
if ( init_pool(&thread_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;
assign_job(&thread_pool, func_create, data);
data = (int *)malloc(sizeof(int));
*data = 2;
num_eat += *data;
assign_job(&thread_pool, func_eat, data);
}
clean:
destory_pool(&thread_pool);
printf("create all: %d\teat all: %d\t left: %d\n", num, num_eat, cakes);
return 0;
}