Libthreadpool

概述

现在进程/线程的创建时间已经大大缩短,但是如果需要频繁的创建进程/线程,并且每个子任务处理时间又非常简短,则进程/线程不断的创建与销毁给系统带来的额外负担也是很大。预分配并发进程或线程技术可用于避免创建与销毁进程/线程所付出的代价,进程/线程一旦创建,就会持续运行。

拿预分配线程来说,设计人员编写主程序时便预先创建一定数目的线程(线程池),当请求到达时,就从线程池中申请一个线程来处理任务,任务完成后,并不是将线程销毁,而是将它返还给线程池,由线程池自行管理。如果线程池中预先分配的线程已经全部分配完毕,但此时又有新的任务请求,则线程池会动态的创建新的线程去适应这个请求。当然,有可能,某些时段应用并不需要执行很多的任务,导致了线程池中的线程大多处于空闲的状态,为了节省系统资源,线程池就需要动态的销毁其中的一部分空闲线程。因此,线程池都需要一个管理者,按照一定的要求去动态的维护其中线程的数目。

线程池创建

线程池的创建包含两个步骤:

  • 初始化线程池句柄结构;
  • 创建线程池(预创建多个线程到池中)。

首先,使用 threadpool_init() 函数来初始化线程池句柄:

#include "threadpool.h" 
 
struct ThreadPool *threadpool_init(); 
 
                返回值:若成功则返回一个指向新分配线程池句柄的指针,否则返回 NULL

迄今为止,我们仅仅分配和初始化了一个结构。我们仍然需要使用线程池句柄的成员函数 create() 创建实际的线程池。

#include "threadpool.h" 
 
int create(struct ThreadPool *tpl, unsigned long long thnum); 
 
                返回值:若成功则返回 0,否则返回 -1

线程池使用了类似于 C++ 面向对象的设计,使用成员函数这一术语并不准确,其实 create() 实际上是线程池句柄结构的一个成员(函数指针)。

创建线程池的一个例子(有7个线程):

#include "threadpool.h" 
 
int 
main(int argc, char *argv[]) 
{ 
    int ret; 
    struct ThreadPool *tpl; 
 
    if ((tpl = threadpool_init()) == NULL) { 
        fprintf(stdout, "threadpool init error.\n"); 
        exit(0); 
    } 
 
    /** create a threadpool with 7 threads */ 
    if ((ret = tpl->create(tpl, 7)) != 0) { 
        fprintf(stdout, "threadpool create error, may be memory is not enough.\n"); 
        threadpool_destroy(tpl); 
        exit(0); 
    } 
 
    /* ... */ 
 
    exit(0); 
}

销毁线程池句柄

#include "threadpool.h" 
 
void threadpool_destroy(struct ThreadPool *tpl); 
 
                返回值:无

把任务交给线程池

成功创建线程池后,现在就可以把任务交给线程池来完成了。分三步可以完成这一过程:

  • 获取一个空任务;
  • 设置任务内容(工作函数);
  • 提交任务;

为什么要分三步呢?因为线程池为了满足各种需要,这样不仅减少提交任务冗长的参数,还可以提高线程池任务管理的灵活性。

#include "threadpool.h" 
 
struct tpl_task *get_task(struct ThreadPool *tpl, int timeout); 
              返回值:若成功则返回 tpl_task 句柄,否则返回 NULL 
 
int submit_task(struct ThreadPool *tpl, struct tpl_task *task); 
              返回值:若成功则返回 0,否则返回 -1

先调用 get_task() 函数,然后设置任务相关信息,比如优先级、任务参数、任务函数等。这就好比去银行存钱/取钱,先要取得一张排队凭证/号码,如果你是 VIP 客户,可以获得高优先级。

调用 submit_task() 函数将任务提交给线程池。

get_task() 函数,指定不同的参数 timeout ,行为会有些不同。

  • 如果 timeout = -1,则调用线程一直阻塞直到获取空闲任务;
  • 如果 timeout = 0,有空闲任务则返回,没有则立即返回 NULL;
  • 如果 timeout > 0,同 timeout = -1,只不过最多等待 timeout 秒;

线程池退出

退出线程池很容易,只要调用如下任一函数即可。

#include "threadpool.h" 
 
void detach(struct ThreadPool *tpl); 
void close(struct ThreadPool *tpl); 
           返回值:无

这两个函数的区别是,detach() 函数等待线程池中的所有任务完成后(包括池中所有线程退出)才返回。close() 函数不等待任务完成,直接让池中所有工作线程退出后返回。

线程池选项

在调用 threadpool_init() 之后,tpl->create() 之前,可以调用 threadpool_option() 修改线程池的默认选项。

#include "threadpool.h" 
 
int threadpool_option(struct ThreadPool *tpl, int option, const void *argument); 
           返回值:成功返回 0

ThreadPool 结构体对调用程序是不透明的,也就是说调用程序并不需要了解内部结构的任何细节。下表总结了线程池的部分属性:

ID 选项 参数 说明
1 TPLOPT_THREAD_STACKSIZE size_t 工作线程堆栈大小
2 TPLOPT_ENABLE_DEBUG NULL 设置调试模式

我们来看下例子:

设置工作线程堆栈大小例子: 
 
size_t stacksize = 2*1024*1024;  /** 2M */ 
if ((ret = threadpool_option(tpl, TPLOPT_THREAD_STACKSIZE, &stacksize)) != 0 ) { 
    fprintf(stdout, "Set thread stack-size error.\n"); 
    exit(0); 
} 
 
设置调试模式: 
 
ret = threadpool_option(tpl, TPLOPT_ENABLE_DEBUG, NULL))

线程池任务管理选项

线程池使用 tpl_task 结构体对任务进行管理,struct tpl_task 有如下成员:

ID NAME TYPE 说明
1 taskid unsigned long long 唯一任务 ID
2 priority unsigned long long 设置任务优先级,默认 0(最小)
3 name char 设置任务名(可选)
4 remark char 设置任务说明(可选)

线程池在创建的同时,会分配一个由 tpl_task 结构体组成的双向循环链表,其长度 ntasknum 由线程池中工作线程数量决定,公式如下:

ntasknum = bthnum * kthnum + athnum

其中 bthnum 是工作线程的数量,kthnum 默认值为 TPL_BOOST_TASK_MULTIPLE,athnum 默认值为 TPL_BOOST_TASK_ADD。kthnum 与 athnum 均可调用 threadpool_option() 进行修改。

也就是说一个有 10 个工作线程的线程池在默认情况下,线程池中有 10 个工作线程、25 个任务,这些任务有 3 种状态:空任务、任务正在被执行、任务等待被执行;工作线程在不断领取新任务并执行,有 2 种状态:等待任务、执行任务。

与任务管理相关宏定义如下:

ID NAME 默认值 说明
1 TPL_BOOST_TASK_MULTIPLE 2 任务倍数
2 TPL_BOOST_TASK_ADD 5 任务基数
3 TPL_BOOST_TASK_NAME 64 任务名字符串长度
4 TPL_BOOST_TASK_REMARK 255 任务备注字符串长度

与任务管理相关高级选项如下:

ID 选项 参数 说明
1 TPLOPT_TASK_MULTIPLE unsigned long long 任务倍数
2 TPLOPT_TASK_ADD unsigned long long 任务基数

任务钩子函数

线程池还有一个非常有用的功能,就是可以设置在满足某种条件的时候回调我们自己设置的函数,即钩子函数。

与任务钩子函数相关高级选项如下:

ID 选项 参数 说明
1 TPLOPT_HOOK_TASK_START 任务开始钩子函数 任务开始执行时执行指定函数
2 TPLOPT_HOOK_TASK_FINISH 任务结束钩子函数 任务完成时执行指定函数
3 TPLOPT_HOOK_TASK_ABORT 任务终止钩子函数 任务终止时执行指定函数
4 TPLOPT_HOOK_TASK_CANCEL 任务取消钩子函数 任务取消时执行指定函数

使用例子:

如何将参数传递给工作函数

参考

目前在网上找的用 C 编写的线程池库代码仅有一个 libthreadpool on sourceforge,但好像并没有维护了,鉴于此,我自己编写一个。供大家分享。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值