首先这个线程池不是我写的,是在github上找到的星标最多的 具体地址是:git@github.com:mbrossard/threadpool.git
其中代码结构比较清晰简单:
包括一个线程池的.c和.h文件,还有几个测试用的文件
之前不了解线程池,也一直想去了解它,这两天有了点时间,就把一些自己的分析贴出来,分享一些,供给和我一样的人做个参考
代码的分析就在代码中了,有问题请各位网友指出,谢谢
如果要测试验证的话,强烈建议在github上去下载源码,因为里面带有makefile,非常方便。
threadpool.c
/*
* Copyright (c) 2016, Mathias Brossard <mathias@brossard.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file threadpool.c
* @brief Threadpool implementation file
*/
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include "threadpool.h"
typedef enum {
immediate_shutdown = 1,
graceful_shutdown = 2
} threadpool_shutdown_t;
/**
* @struct threadpool_task
* @brief the work struct
*
* @var function Pointer to the function that will perform the task.
* @var argument Argument to be passed to the function.
*/
typedef struct {
void (*function)(void *);
void *argument;
} threadpool_task_t;
/**
* @struct threadpool
* @brief The threadpool struct
*
* @var notify Condition variable to notify worker threads.
* @var threads Array containing worker threads ID.
* @var thread_count Number of threads
* @var queue Array containing the task queue.
* @var queue_size Size of the task queue.
* @var head Index of the first element.
* @var tail Index of the next element.
* @var count Number of pending tasks
* @var shutdown Flag indicating if the pool is shutting down
* @var started Number of started threads
*/
struct threadpool_t {
pthread_mutex_t lock;
pthread_cond_t notify;
pthread_t *threads;
threadpool_task_t *queue;
int thread_count;
int queue_size;
int head;
int tail;
int count;
int shutdown;
int started;
};
/**
* @function void *threadpool_thread(void *threadpool)
* @brief the worker thread
* @param threadpool the pool which own the thread
*/
static void *threadpool_thread(void *threadpool);
int threadpool_free(threadpool_t *pool);
threadpool_t *threadpool_create(int thread_count, int queue_size, int flags)
{
threadpool_t *pool;
int i;
/*
* flags目前没有用到,用这种方式避免编译warning
*/
(void) flags;
/*
* 这是对应参数的判断
*/
if(thread_count <= 0 || thread_count > MAX_THREADS || queue_size <= 0 || queue_size > MAX_QUEUE) {
return NULL;
}
/*
* 这里动态申请一个pool指针,指向threadpool_t结构体
*/
if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) {
goto err;
}
/* Initialize */
pool->thread_count = 0;
pool->queue_size = queue_size;
pool->head = pool->tail = pool->count = 0;
/*
* 这里shutdown 和 started的含义是什么?
*/
pool->shutdown = pool->started = 0;
/* Allocate thread and task queue */
/*
* 这里malloc的内存是为了存放thread id用的,在用pthread_create创建线程的时候需要存放线程的ID,类型是pthread_t类型
* queue是用来存放任务task的一块内存,任务task的结构体就是函数指针和对应的参数
*/
pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count);
pool->queue = (threadpool_task_t *)malloc
(sizeof(threadpool_task_t) * queue_size);
/* Initialize mutex and conditional variable first */
/*
* 这里初始化pool->lock这个mutex,这个和main函数中的mutex是不同的.
* 看下pthread_cond_init这个是用来做什么用的
* 关于pthread_cond_init等一系列函数的具体使用和原理可以参考网友的一些文章
* 特别是pthread_cond_wati函数的原理,我后面也会提到
*/
if((pthread_mutex_init(&(pool->lock), NULL) != 0) ||
(pthread_cond_init(&(pool->notify), NULL) != 0) ||
(pool->threads == NULL) ||
(pool->queue == NULL)) {
goto err;
}
/* Start worker threads */
/*
* 创建线程可以看到线程ID都存放在pool->threads这个数组中,线程函数是threadpool_thread,
* 传入线程函数的参数是pool
* 当然如果线程创建不成功则需要destory整个pool线程池
* 每创建一个线程则thread_count 需要累加,还有started也需要累加,这里猜测started表示目前开启的空闲线程的数目吧。
*/
for(i = 0; i < thread_count; i++) {
if(pthread_create(&(pool->threads[i]), NULL,
threadpool_thread, (void*)pool) != 0) {
threadpool_destroy(pool, 0);
return NULL;
}
pool->thread_count++;
pool->started++;
}
/*
* 返回的是线程池pool这个指针
*/
return pool;
err:
if(pool) {
threadpool_free(pool);
}
return NULL;
}
/*
* 参数threadpool_t * pool是传入的线程池的指针,刚开始初始化为null,意味着后面有对它进行动态赋值
* 参数function是参数为void* 类型的函数指针,
* 参数argument是参数,是前面function的参数
* 参数flags目前还不知道是做什么的,我们现在进去看看
*/
int threadpool_add(threadpool_t *pool, void (*function)(void *),
void *argument, int flags)
{
int err = 0;
int next;
/*
* flags目前好像没有用,这里防止编译报warning,直接这样写了
*/
(void) flags;
/*
* 如果pool或者function为null的话这里直接退出,并且返回响应的错误码
*/
if(pool == NULL || function == NULL) {
return threadpool_invalid;
}
/*
* 这里前面的lock起到作用了,为什么要用mutex lock,
* 记住:凡是涉及到 不同的线程中对同一块内存的操作,都是需要加锁的
* 当然这个 主要是写操作,这里有人有疑问,threadpool_add是在main中调用的
* 它是怎么引起‘竞争’操作的呢?在我们调用threadpool_add添加任务到队列的时候,
* 一旦添加到队列,那么空闲的线程就会去处理这个任务队列中的任务,那么下面的
* pool->tail这样的共享变量就可能会被进程和线程同时去访问或者修改,所以这里要
* 用mutex lock来避免因‘竞争’导致的访问异常
*/
if(pthread_mutex_lock(&(pool->lock)) != 0) {
return threadpool_lock_failure;
}
/*
* pool->tail刚开始初始化为0,这里先保存task队列的下一个索引
* 如果tail到最大的话就又回到第一个了,是循环去做的索引
*/
next = (pool->tail + 1) % pool->queue_size;
do {
/* Are we full ? */
/*
* 如果当前的count已经等于队列的size了,说明队列已经满了
*/
if(pool->count == pool->queue_size) {
err = threadpool_queue_full;
break;
}
/* Are we shutting down ? */
/*
* 这里看如果是shutdown状态的话,就break
*/
if(pool->shutdown) {
err = threadpool_shutdown;
break;
}
/* Add task to queue */
/*
* 这里就是此函数的主要作用,将处理函数和处理参数放到task队列中去
* 然后队列的尾巴索引做下修改,任务队列的count计数也做下累加
* 和前面任务队列是否满的判断也遥相呼应
*/
pool->queue[pool->tail].function = function;
pool->queue[pool->tail].argument = argument;
pool->tail = next;
pool->count += 1;
/* pthread_cond_broadcast */
/*
* 这里通过pthread_cond_signal来使pool->notify置位有信号状态,
* 使在等待信号的一个阻塞线程脱离阻塞状态继续执行,注意,
* 它和pthread_cond_brocast的区别,pthread_cond_brocast是唤醒
* 所有因为pool->notify没有信号而正在阻塞的线程。
* 因为这里增加一个task,所以只用去通知一个线程来处理这个task就可以
*/
if(pthread_cond_signal(&(pool->notify)) != 0) {
err = threadpool_lock_failure;
break;
}
} while(0);/* 不知道这里do{}while(0)有何用意 */
/*
* 通知完之后unlock
*/
if(pthread_mutex_unlock(&pool->lock) != 0) {
err = threadpool_lock_failure;
}
return err;
}
int threadpool_destroy(threadpool_t *pool, int flags)
{
int i, err = 0;
if(pool == NULL) {
return threadpool_invalid;
}
/*
* 还是那句老话,如果需要在函数中访问其他线程也有可能访问的内存地址
* 特别是写访问,都需要加入lock
*/
if(pthread_mutex_lock(&(pool->lock)) != 0) {
return threadpool_lock_failure;
}
do {
/* Already shutting down */
if(pool->shutdown) {
err = threadpool_shutdown;
break;
}
/*
* 这里根据参数flags来确定shutdown的原因,毕竟你要把线程池destory掉,
* 你得告诉别人最终shutdown的原因吧。
*/
pool->shutdown = (flags & threadpool_graceful) ?
graceful_shutdown : immediate_shutdown;
/* Wake up all worker threads */
/*
* 你要destory掉线程池的话,你需要把目前正在阻塞的线程都唤醒,然后正常退出吧,
* 这里pthread_cond_broadcast会将所有因为pool->notify没有信号而导致阻塞的线程
* 给唤醒,然后unlock
*/
if((pthread_cond_broadcast(&(pool->notify)) != 0) ||
(pthread_mutex_unlock(&(pool->lock)) != 0)) {
err = threadpool_lock_failure;
break;
}
/* Join all worker thread */
/*
* 这里pthread_join会等待线程的结束.
*
* 这里提一点,有人说pthread_join不用调用啊,前面已经把正在等待的线程给唤醒,
* 然后线程自然退出不就OK吗,资源正常释放就好了啊,为什么还要pthread_join,
* 其实线程在释放资源的时候是需要一定的时间,虽然这个时间很短,但是如果,
* 打个比方,你在main中不去等待子线程的资源全部释放完,就直接return了,那么
* 可能会造成线程资源的不完全回收(这个是我自己的说法),就类似于Windows中的
* WaitForSingnalObject等待线程结束一样
*/
for(i = 0; i < pool->thread_count; i++) {
if(pthread_join(pool->threads[i], NULL) != 0) {
err = threadpool_thread_failure;
}
}
} while(0);
/* Only if everything went well do we deallocate the pool */
if(!err) {
threadpool_free(pool);
}
return err;
}
int threadpool_free(threadpool_t *pool)
{
if(pool == NULL || pool->started > 0) {
return -1;
}
/* Did we manage to allocate ? */
if(pool->threads) {
free(pool->threads);
free(pool->queue);
/* Because we allocate pool->threads after initializing the
mutex and condition variable, we're sure they're
initialized. Let's lock the mutex just in case. */
/*
* 这里在mutex_destroy之前,我没想到竟然还要pthread_mutex_lock?
* 这个是什么情况呢?有谁知道的可以告知一下
*/
pthread_mutex_lock(&(pool->lock));
pthread_mutex_destroy(&(pool->lock));
pthread_cond_destroy(&(pool->notify));
}
free(pool);
return 0;
}
/*
* threadpool_thread是个线程函数,我们创建了一个线程,那么线程的运行地址就是由这个函数指定的
* 进入运行地址之后,首先for循环我们先不管,看马上有lock函数将下面的代码加锁,这是我们的第一感觉和印象,
* 我们创建线程的主要目的是去执行task,这里的pool->count就是指目前正在等待执行的task的数量,
* 如果数量是0的话我们就一直等待,等待的函数就是pthread_cond_wait,它会一直等待直到有代码去触发pthread_cond_signal
* 才能将这个等待的部分给唤醒,在哪里去唤醒的?就是在threadpool_add的时候去唤醒的
*/
static void *threadpool_thread(void *threadpool)
{
threadpool_t *pool = (threadpool_t *)threadpool;
threadpool_task_t task;
for(;;) {
/* Lock must be taken to wait on conditional variable */
pthread_mutex_lock(&(pool->lock));
/* Wait on condition variable, check for spurious wakeups.
When returning from pthread_cond_wait(), we own the lock. */
while((pool->count == 0) && (!pool->shutdown)) {
pthread_cond_wait(&(pool->notify), &(pool->lock));
}
if((pool->shutdown == immediate_shutdown) ||
((pool->shutdown == graceful_shutdown) &&
(pool->count == 0))) {
break;
}
/* Grab our task */
task.function = pool->queue[pool->head].function;
task.argument = pool->queue[pool->head].argument;
pool->head = (pool->head + 1) % pool->queue_size;
pool->count -= 1;
/* Unlock */
pthread_mutex_unlock(&(pool->lock));
/* Get to work */
(*(task.function))(task.argument);
}
/*
* 空闲线程的计数要减一
*/
pool->started--;
pthread_mutex_unlock(&(pool->lock));
/*
*显式调用pthread_exit来退出线程,如果指定了退出参数,那么这个退出参数
可以被pthread_join获得
*/
pthread_exit(NULL);
return(NULL);
}
threadpoo.h
/*
* Copyright (c) 2016, Mathias Brossard <mathias@brossard.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _THREADPOOL_H_
#define _THREADPOOL_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file threadpool.h
* @brief Threadpool Header File
*/
/**
* Increase this constants at your own risk
* Large values might slow down your system
*/
#define MAX_THREADS 64
#define MAX_QUEUE 65536
typedef struct threadpool_t threadpool_t;
typedef enum {
threadpool_invalid = -1,
threadpool_lock_failure = -2,
threadpool_queue_full = -3,
threadpool_shutdown = -4,
threadpool_thread_failure = -5
} threadpool_error_t;
typedef enum {
threadpool_graceful = 1
} threadpool_destroy_flags_t;
/**
* @function threadpool_create
* @brief Creates a threadpool_t object.
* @param thread_count Number of worker threads.
* @param queue_size Size of the queue.
* @param flags Unused parameter.
* @return a newly created thread pool or NULL
*/
threadpool_t *threadpool_create(int thread_count, int queue_size, int flags);
/**
* @function threadpool_add
* @brief add a new task in the queue of a thread pool
* @param pool Thread pool to which add the task.
* @param function Pointer to the function that will perform the task.
* @param argument Argument to be passed to the function.
* @param flags Unused parameter.
* @return 0 if all goes well, negative values in case of error (@see
* threadpool_error_t for codes).
*/
int threadpool_add(threadpool_t *pool, void (*routine)(void *),
void *arg, int flags);
/**
* @function threadpool_destroy
* @brief Stops and destroys a thread pool.
* @param pool Thread pool to destroy.
* @param flags Flags for shutdown
*
* Known values for flags are 0 (default) and threadpool_graceful in
* which case the thread pool doesn't accept any new tasks but
* processes all pending tasks before shutdown.
*/
int threadpool_destroy(threadpool_t *pool, int flags);
#ifdef __cplusplus
}
#endif
#endif /* _THREADPOOL_H_ */
测试用的shutdown.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include "threadpool.h"
#define THREAD 4
#define SIZE 8192
threadpool_t *pool;
int left;
pthread_mutex_t lock;
int error;
void dummy_task(void *arg) {
usleep(100);
pthread_mutex_lock(&lock);
left--;
printf("left = %d\r\n",left);
pthread_mutex_unlock(&lock);
}
int main(int argc, char **argv)
{
int i;
/*
* 初始化pthread_mutex_t,注意这里lock要和后面线程池中做同步时候用的lock要区分开
* 两个用的地方不一样的,这里是为了在同一个函数中dummy_task做同步用的
*/
pthread_mutex_init(&lock, NULL);
/* Testing immediate shutdown */
/*
* 创建线程池,thread_create,参数THREAD是线程数
* SIZE 是等待执行的队列的大小,也就是'等待池’的大小,
* threadpool_create的作用是创建THREAD个线程,等待任务的到来,
* 任务到来的时候就先放入池子里面,如果当前有线程在睡眠,
* 那么通知此线程去执行这个任务,可以考虑这个场景:
* UDP数据通信,有数据通过UDP的方式发送到server端,server端不断的去检测是否有数据
* 有数据过来需要处理,如果不采用线程池的情况下,数据过来我里面去处理,在处理的过程中再有数据过来,我就放弃处理,
* 处理好的数据再通过发送API发送出去
* 这么做的话就需要当前的数据处理完再去处理其他数据,中间处理过程中的数据都丢掉了,线程池的作用就是
* 1:将需要处理的数据保存下来(当然这个例程中并没有这个保存过程)
* 2:开启好几个线程去并行处理(如果处理函数中有共享的‘内存’,这个内存包括共享的变量,malloc出来的内存,
* 这个时候需要加锁,加锁的部分就是串行的,加锁的代码越短越好,这个应该想的到)
* 3: 一旦有线程空闲出来就去任务队列里面取出一个任务去执行
*
*/
left = SIZE;
pool = threadpool_create(THREAD, SIZE, 0);
for(i = 0; i < SIZE; i++) {
assert(threadpool_add(pool, &dummy_task, NULL, 0) == 0);
}
assert(threadpool_destroy(pool, 0) == 0);
assert(left > 0);
/* Testing graceful shutdown */
left = SIZE;
pool = threadpool_create(THREAD, SIZE, 0);
for(i = 0; i < SIZE; i++) {
assert(threadpool_add(pool, &dummy_task, NULL, 0) == 0);
}
assert(threadpool_destroy(pool, threadpool_graceful) == 0);
assert(left == 0);
pthread_mutex_destroy(&lock);
return 0;
}