Linux 基于C的线程池实现

首先这个线程池不是我写的,是在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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值