【源码剖析】threadpool —— 基于 pthread 实现的简单线程池

本文介绍了基于pthread的简单线程池实现,包括线程池的工作原理、数据结构、对外接口和内部辅助函数。通过创建、添加任务和销毁线程池的函数,阐述线程池如何提高任务处理效率。
摘要由CSDN通过智能技术生成

博客新地址:https://github.com/AngryHacker/articles/blob/master/src/code_reading/threadpool.md

线程池介绍

线程池可以说是项目中经常会用到的组件,在这里假设读者都有一定的多线程基础,如果没有的话不妨在这里进行了解:POSIX 多线程基础

线程池是什么?我的简单理解是有一组预先派生的线程,然后有一个管理员来管理和调度这些线程,你只需不断把需要完成的任务交给他,他就会调度线程的资源来帮你完成。

那么管理员是怎么做的呢?一种简单的方式就是,管理员管理一个任务的队列,如果收到新的任务,就把任务加到队列尾。每个线程盯着队列,如果队列非空,就去队列头拿一个任务来处理(每个任务只能被一个线程拿到),处理完了就继续去队列取任务。如果没有任务了,线程就休眠,直到任务队列不为空。如果这个管理员更聪明一点,他可能会在没有任务或任务少的时候减少线程的数量,任务处理不过来的时候增加线程的数量,这样就实现了资源的动态管理。

那么任务是什么呢?以后台服务器为例,每一个用户的请求就是一个任务,线程不断的在请求队列里取出请求,完成后继续处理下一个请求。

简单图示为:
threadpool

线程池有一个好处就是减少线程创建和销毁的时间,在任务处理时间比较短的时候这个好处非常显著,可以提升任务处理的效率。

线程池实现

这里介绍的是线程池的一个简单实现,在创建的时候预先派生指定数量的线程,然后去任务队列取添加进来的任务进行处理就好。

作者说之后会添加更多特性,我们作为学习之后就以这个版本为准就好了。

项目主页:threadpool

数据结构

主要有两个自定义的数据结构

threadpool_task_t

用于保存一个等待执行的任务。一个任务需要指明:要运行的对应函数及函数的参数。所以这里的 struct 里有函数指针和 void 指针。

typedef struct {
   
    void (*function)(void *);
    void *argument;
} threadpool_task_t;
thread_pool_t

一个线程池的结构。因为是 C 语言,所以这里任务队列是用数组,并维护队列头和队列尾来实现。

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;              /* 正在运行的线程数 */
};
函数
对外接口
  • threadpool_t *threadpool_create(int thread_count, int queue_size, int flags); 创建线程池,用 thread_count 指定派生线程数,queue_size 指定任务队列长度,flags 为保留参数,未使用。
  • int threadpool_add(threadpool_t *pool, void (*routine)(void *),void *arg, int flags); 添加需要执行的任务。第二个参数为对应函数指针,第三个为对应函数参数。flags 未使用。
  • int threadpool_destroy(threadpool_t *pool, int flags); 销毁存在的线程池。flags 可以指定是立刻结束还是平和结束。立刻结束指不管任务队列是否为空,立刻结束。平和结束指等待任务队列的任务全部执行完后再结束,在这个过程中不可以添加新的任务。
内部辅助函数
  • static void *threadpool_thread(void *threadpool); 线程池每个线程所执行的函数。
  • int threadpool_free(threadpool_t *pool); 释放线程池所申请的内存资源。

线程池使用

编译

参考项目根目录下的 Makefile, 直接用 make 编译。

测试用例

项目提供了三个测试用例(见 threadpool/test/),我们可以以此来学习线程池的用法并测试是否正常工作。这里提供其中一个:

#define THREAD 32
#define QUEUE  256

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>

#include "threadpool.h"

int tasks = 0, done = 0;
pthread_mutex_t lock;

void dummy_task(void *arg) {
   
    usleep(10000);
    pthread_mutex_lock(&lock);
    /* 记录成功完成的任务数 */
    done++;
    pthread_mutex_unlock(&lock);
}

int main(int argc, char **argv)
{
   
    threadpool_t *pool;

    /* 初始化互斥锁 */
    pthread_mutex_init(&lock, NULL);

    /* 断言线程池创建成功 */
    assert((pool = threadpool_create(THREAD, QUEUE, 0)) != NULL);
    fprintf(stderr, "Pool started with %d threads and "
            "queue size of %d\n", THREAD, QUEUE);

    /* 只要任务队列还没满,就一直添加 */
    while(threadpool_add(pool, &dummy_task, NULL, 0) == 0) {
   
        pthread_mutex_lock(&lock);
        tasks++;
        pthread_mutex_unlock(&lock);
    }

    fprintf(stderr, "Added %d tasks\n", tasks);

    /* 不断检查任务数是否完成一半以上,没有则继续休眠 */
    while((tasks / 2) > done) {
   
        usleep(10000);
    }
    /* 这时候销毁线程池,0 代表 immediate_shutdown */
    assert(threadpool_destroy(pool, 0) == 0);
    fprintf(stderr, "Did %d tasks\n"
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值