Pthread - 線程池(thread pool)實現

Pthread - 線程池(thread pool)實現

線程池簡介

線程池在多線程編程中經常要用到,其基本模型仍是生產者/消費者模型,線程池一般由線程池管理器(ThreadPool),工作線程(PoolWorker),任務( Task),任務隊列(TaskQueue)四部分組成,其中
線程池管理器(ThreadPool):用於創建並管理線程池,包括 創建線程池,銷燬線程池,添加新任務;
工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務;
任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等;
任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩衝機制。
這裏實現的線程池的任務隊列爲單向鏈表,支持的功能有:
添加任務時,線程池中工作線程數可以動態增長到某一閾值
任務執行完畢時,可以動態銷燬線程池中的線程

結構體定義說明

任務(Task)結構體定義:

typedef struct task {
    TASK_ROUTINE run; // task handler
    TASK_PARA_TYPE arg; //para for handler "run"
    struct task* next; // pointer to the next task
}task_t;

run爲任務接口函數,其參數爲arg,next 爲指向下一任務的指針。
訪問控制結構體定義:

typedef struct condition {
    pthread_mutex_t p_mutex; //mutex
    pthread_cond_t p_cond; //condition variable
}cond_t;

該結構體封裝了 Mutex 和 Condition variable 用於控制任務執行。
線程池(Threadpool)結構體定義

typedef struct threadpool {
    cond_t ready; // mutex and condition variable for thread pool
    task_t *first;  // pointer to the first task in the thread pool
    task_t *last; // point to the last past task in the thread pool
    int threadcnt; // thread count at the present
    int idle; //idle thread count at the present
    int max_threads; // max threads for thread pool
    int quit; // set 1 when destroying thread pool
}threadpool_t;

該結構體封裝了線程池的任務隊列頭尾指針,工作線程閾值,當前工作線程數目,空閒工作線程數目,以及線程退出標誌。

工程文件說明

pool_util.h - 功能函數和宏定義
condition.h,condition.c - Mutex 和 Condition variable 操作封裝 >threadpool.h,threadpool.c - 線程池操作封裝
makefile - 編譯文件

condition.h 定義:

#pragma once

#include <sys/time.h>
#include <pthread.h>

typedef struct condition {
    pthread_mutex_t p_mutex; //mutex
    pthread_cond_t p_cond; //condition variable
}cond_t;


int cond_init(cond_t* cond); //initial Mutex and Condition variable
int cond_destroy(cond_t* cond); // deallocate Mutex and Condition variable
int cond_lock(cond_t* cond); // acquire Mutex lock
int cond_unlock(cond_t* cond); //// release Mutex lock
int cond_wait(cond_t* cond); // wait for a condition
int cond_timedwait(cond_t* cond, const struct timespec *tv); // wait for a condition for a specified time
int cond_signal(cond_t* cond); // send  signal to a waiting thread
int cond_broadcast(cond_t* cond); // send  signal to all waiting thread

pthreadpool.h 定義:

#pragma once

#include "condition.h"

typedef void* (*TASK_ROUTINE) (void*);
typedef void* TASK_PARA_TYPE;

typedef struct task {
    TASK_ROUTINE run; // task handler
    TASK_PARA_TYPE arg; //para for handler "run"
    struct task* next; // pointer to the next task
}task_t;


typedef struct threadpool {
    cond_t ready; // mutex and condition variable for thread pool
    task_t *first;  // pointer to the first task in the thread pool
    task_t *last; // point to the last past one task in the thread pool
    int threadcnt; // thread count at the present
    int idle; //idle thread count at the present
    int max_threads; // max threads for thread pool
    int quit; // set 1 when destroying thread pool
}threadpool_t;

//initialize thread pool
void threadpool_init(threadpool_t* pool, int max_threads);

//deallocate thread pool
void threadpool_destroy(threadpool_t *pool);

// add a task to thread pool
void threadpool_add_task(threadpool_t *poo, TASK_ROUTINE mytask, TASK_PARA_TYPE arg);

接口實現

condition.c 定義:

#include "condition.h"
#include "pool_util.h"
int cond_init(cond_t* cond)
{
    int ret;
    ret = pthread_mutex_init(&cond->p_mutex, NULL);
    if(ret) {
        ERROR("pthread_mutex_init", ret);
    }

    ret = pthread_cond_init(&cond->p_cond, NULL);
    if(ret) {
        ERROR("pthread_cond_init", ret);
    }
    return 0;
}

int cond_destroy(cond_t* cond)
{
    int ret;
    ret = pthread_mutex_destroy(&cond->p_mutex);
    if(ret) {
        ERROR("pthread_mutex_destroy", ret);
    }

    ret = pthread_cond_destroy(&cond->p_cond);
    if(ret) {
        ERROR("pthread_cond_destroy", ret);
    }
    return 0;
}

int cond_lock(cond_t* cond)
{
    return pthread_mutex_lock(&cond->p_mutex);
}

int cond_unlock(cond_t* cond)
{
    return pthread_mutex_unlock(&cond->p_mutex);
}

int cond_wait(cond_t* cond)
{
    return pthread_cond_wait(&cond->p_cond, &cond->p_mutex);
}

int cond_timedwait(cond_t* cond, const struct timespec *ts)
{
    return pthread_cond_timedwait(&cond->p_cond, &cond->p_mutex, ts);
}

int cond_signal(cond_t* cond)
{
    return pthread_cond_signal(&cond->p_cond);
}

int cond_broadcast(cond_t* cond)
{
    return pthread_cond_broadcast(&cond->p_cond);
}

threadpool.c 定義:

#include <unistd.h>
#include <errno.h>

#include "threadpool.h"
#include "pool_util.h"

void* thread_routine(void* arg) {
    pthread_t tid = pthread_self();
    printf("Thread %#lx starting\n", (size_t)tid);
    threadpool_t *pool = (threadpool_t*)arg;
    int timedout;

    while(1) {
        timedout = 0;
        cond_lock(&pool->ready);

        pool->idle++;
        //waiting for new task or the destroy of thread pool
        while((NULL==pool->first) &&  (0==pool->quit)) {
        //while((NULL==pool->first)) {
            printf("Thread %#lx waiting\n", (size_t)tid);
            //blocked wait
            //cond_wait(&pool->ready);

            //impletement timedout wait
            struct timeval tv;
                struct timespec ts;
                gettimeofday(&tv, NULL);
                ts.tv_sec = tv.tv_sec + 2;
                ts.tv_nsec = 0;
            int ret = cond_timedwait(&pool->ready, &ts);

            if(ETIMEDOUT == ret) {
                printf("Thread %#lx waiting timedout\n", (size_t)tid);
                timedout = 1;
                break;
            }
        }

        pool->idle--;

        // new task
        if(pool->first) {
            // extract a task from the head of the queue
            task_t *tk = pool->first;
            pool->first = tk->next;     

            //It takes some time to excute task, unlock first to permit 
            //other producers to add task, and other consumers to enter the loop 
            cond_unlock(&pool->ready);
            //execute task
            tk->run(tk->arg);
            free(tk);
            cond_lock(&pool->ready);
        }

        // the destroy of thread pool
        if(pool->quit && NULL==pool->first) {
            pool->threadcnt--;
            if(0 == pool->threadcnt) 
                cond_signal(&pool->ready);
            cond_unlock(&pool->ready);//do not forget unlock when breaking out the loop
            break;
        }


        // wait timedout
        if(timedout && NULL==pool->first) {
            pool->threadcnt--;
            cond_unlock(&pool->ready);//do not forget unlock when breaking out the loop
            break;
        }

        cond_unlock(&pool->ready);
    }
    printf("Thread %#lx exiting\n", (size_t)tid);
    return NULL;
}

//initialize thread pool
void threadpool_init(threadpool_t* pool, int max_threads)
{
    cond_init(&pool->ready);
    pool->first = pool->last = NULL;
    pool->threadcnt = pool->idle = 0;
    pool->max_threads = max_threads;
    pool->quit = 0;
}

//deallocate thread pool
void threadpool_destroy(threadpool_t *pool)
{
    if(pool->quit) {
        return; 
    }

    cond_lock(&pool->ready);

    pool->quit = 1;
    if(pool->threadcnt) {
        //the working thread cannot receive the broadcast notification
        if(pool->idle)
            cond_broadcast(&pool->ready);

        while(pool->threadcnt) {
            //printf("Waiting thread(s) to exit\n");
            cond_wait(&pool->ready);
        }
    }

    cond_unlock(&pool->ready);
    cond_destroy(&pool->ready);
}

// add a task to thread pool
void threadpool_add_task(threadpool_t *pool, TASK_ROUTINE mytask, TASK_PARA_TYPE arg)
{
    task_t* newtask = (task_t*)malloc(sizeof(task_t));
    newtask->run = mytask;
    newtask->arg = arg;
    newtask->next = NULL;

    cond_lock(&pool->ready);

    // insert newtask at the end of the queue
    if(pool->first) {
        pool->last->next = newtask;
    } else {
        pool->first = newtask;
    }
    pool->last = newtask;

    // notify waiting threads
    if(pool->idle > 0) {
        cond_signal(&pool->ready);
    } else if(pool->threadcnt < pool->max_threads) { //add new thread if not reaching limit
        pthread_t tid;
        int ret;
        if((ret=pthread_create(&tid, NULL, thread_routine, (void*)pool))) {
            ERROR("pthread_create", ret);
        }
        pool->threadcnt++;
    }

    cond_unlock(&pool->ready);
}

main.c 定義:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "threadpool.h"

#define DEFAULT_TASK_CNT    10
#define DEFAULT_THREAD_CNT  3


void* task_routine(void* arg) {
    pthread_t tid = pthread_self();
    int task_id = *(int*)arg;
    free(arg);
    printf("Thread %#lx working on task %d\n", (size_t)tid, task_id);
    sleep(1);
    return NULL;
}

int main(int argc, char** argv)
{
    int taskcnt, threadcnt, ch;
    taskcnt = DEFAULT_TASK_CNT;
    threadcnt = DEFAULT_THREAD_CNT;
    char* prog = argv[0];
        while ((ch = getopt(argc, argv, "t:k:")) != -1) {
             switch (ch) {
             case 't':
                     taskcnt = atoi(optarg);
             break;
             case 'k':
             threadcnt = atoi(optarg);
             break;
             case '?':
             default:
                     printf("Usage: %s [-k threadcnt] [-t taskcnt]\n"
                "\tdefault threadcnt=3, taskcnt=10\n", prog);
             exit(EXIT_FAILURE);
             }
        }

    threadpool_t pool;
    threadpool_init(&pool, threadcnt);

    int i;
    for(i=0; i<taskcnt; ++i) {
        void *arg = malloc(sizeof(int));
        memcpy(arg, &i, sizeof(int));
        threadpool_add_task(&pool, task_routine, arg);
    }

    threadpool_destroy(&pool);

    return 0;
}

makefile 文件:

.PHONY: all clean

CC=gcc
CFLAGS=-Wall -g
LIB=-lpthread
OBJS=main.o threadpool.o condition.o
BIN=proc

all:$(BIN)
$(BIN):$(OBJS)
    $(CC) $(CFLAGS) -o $@ $^ $(LIB)

# to acquire the rules
#use: gcc -MM *.c
condition.o: condition.c condition.h pool_util.h
main.o: main.c threadpool.h condition.h
threadpool.o: threadpool.c threadpool.h condition.h pool_util.h

clean:
    @rm -rf *.dSYM *.o $(BIN)

PS:編寫 Makefile 時可以通過 gcc -MM *.c命令源文件的依賴關係
這裏寫圖片描述
運行效果:
這裏寫圖片描述

你也可以改變工作線程數和任務數:
這裏寫圖片描述

參考鏈接:
線程池的原理及實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章