C 实现有追求的线程池 后续
引言 -_- 还是老套路开局
很久以前写过一个有追求的线程池 -> C 实现有追求的线程池 探究
讲述的是一种思路, 并且实现了. 可以一用. 最近在详细搞simplec 框架. 准备发布个正式版.
刚好顺带优化一下这个线程池.优化的结果有如下几个方面.
1). 更加美观合理的api
2). pthread线程api 优化
3). 在解决惊群的基础上, 更进一步, 精度定位.
4). 增加了更多的安全性代码
扯淡一点, 线程池对于历史语言C, C++中应用的场景并不多. 可以用线程解决的, 都可以用消息队列解决.
当然线程有个不一样的属性就是可抢占性. 当你追求性能的时候, 那么这个基本满足不了.
至少现代的游戏框架设计中抢占式任务没见有这个必要.
以下容许我分段阐述思路, scthreads.h
#ifndef _H_SIMPLEC_SCTHREADS
#define _H_SIMPLEC_SCTHREADS #include <schead.h> //
// 这是个线程池的库. 支持异步取消 也加过一些线程帮助库
// typedef struct threads * threads_t; //
// async_run - 开启一个自销毁的线程 运行 run
// run : 运行的主体
// arg : run的参数
// return : >= Success_Base 表示成功
//
extern int async_run(die_f run, void * arg); //
// threads_create - 创建一个线程池处理对象
// return : 返回创建好的线程池对象, NULL表示失败
//
extern threads_t threads_create(void); //
// threads_delete - 异步销毁一个线程池对象
// pool : 线程池对象
// return : void
//
extern void threads_delete(threads_t pool); //
// threads_add - 线程池中添加要处理的任务
// pool : 线程池对象
// run : 运行的执行题
// arg : run的参数
// return : void
//
extern void threads_add(threads_t pool, die_f run, void * arg); #endif // !_H_SIMPLEC_SCTHREADS
通过上面 可以说明 1). 更加美观合理的api 因为内部使用宏来确定最优线程数. 不需要玩家自己指定.当然这个数值偏小.
前言 -_- 来点开胃点心
有时候我们使用pthread 线程的时候, 步骤有点小繁琐. 我们其实不太需要知道有这个线程, 这个线程执行完毕之后做什么.
只希望简单的帮我异步的执行一个方法就可以了. 这里设计了 thread_run 函数.
typedef void (* die_f)(void * node);
extern int async_run(die_f run, void * arg);
详细的设计套路. 如下
#include <pthread.h> // 运行的主体
struct func {
die_f run;
void * arg;
}; // thread_run 中 pthread 执行的实体
static void * _run(void * arg) {
struct func * func = arg;
func->run(func->arg);
free(arg);
return NULL;
} //
// async - 开启一个自销毁的线程 运行 run
// run : 运行的主体
// arg : run的参数
// return : >= Success_Base 表示成功
//
int
async_run(die_f run, void * arg) {
pthread_t tid;
pthread_attr_t attr;
struct func * func = malloc(sizeof(struct func));
if (NULL == func)
RETURN(Error_Alloc, "malloc sizeof(struct func) is error"); func->run = run;
func->arg = arg; // 构建pthread 线程奔跑起来
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (pthread_create(&tid, &attr, _run, func) < ) {
free(func);
pthread_attr_destroy(&attr);
RETURN(Error_Base, "pthread_create error run, arg = %p | %p.", run, arg);
} pthread_attr_destroy(&attr);
return Success_Base;
}
这里扯一点, 第一个是我常用的通用错误枚举.
//
// flag_e - 全局操作基本行为返回的枚举, 用于判断返回值状态的状态码
// >= 0 标识 Success状态, < 0 标识 Error状态
//
typedef enum {
Success_Exist = +, //希望存在,设置之前已经存在了.
Success_Close = +, //文件描述符读取关闭, 读取完毕也会返回这个
Success_Base = +, //结果正确的返回宏 Error_Base = -, //错误基类型, 所有错误都可用它, 在不清楚的情况下
Error_Param = -, //调用的参数错误
Error_Alloc = -, //内存分配错误
Error_Fd = -, //文件打开失败
Error_TOUT = -, //超时错误
} flag_e;
项目实战中运用的很好. 基本一个函数返回的错误就那些.
再扯第二点. 在我们使用 pthread_attr_init的时候posix线程推荐我们立即也必须调用 pthread_attr_destroy.
保证你自己的东西自己释放. 实际上 pthread_*_destroy 这类函数只是返回当前线程状态, 不涉及资源销毁内容.
再扯第三点, 好用的RETURN宏, 还是挺飘的.
//
// 控制台输出完整的消息提示信息, 其中fmt必须是 "" 包裹的字符串
// CERR -> 简单的消息打印
// CERR_EXIT -> 输出错误信息, 并推出当前进程
// CERR_IF -> if语句检查, 如果符合标准错误直接退出
//
#ifndef _H_CERR
#define _H_CERR #define CERR(fmt, ...) \
fprintf(stderr, "[%s:%s:%d][errno %d:%s]" fmt "\n",\
__FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) #define CERR_EXIT(fmt,...) \
CERR(fmt, ##__VA_ARGS__), exit(EXIT_FAILURE) #define CERR_IF(code) \
if((code) < ) \
CERR_EXIT(#code) //
// RETURN - 打印错误信息, 并return 返回指定结果
// val : return的东西, 当需要 return void; 时候填 ',' 就过 or NIL
// fmt : 双引号包裹的格式化字符串
// ... : fmt中对应的参数
// return : val
//
#define NIL
#define RETURN(val, fmt, ...) \
do {\
CERR(fmt, ##__VA_ARGS__);\
return val;\
} while() #endif
## 是为了解决, 可变参数中只有一个参数的问题(... 为 empty 没有内容, GCC编译器不过).
NIL 是为了解决 return void; 语法 被 RETURN(NIL) 这种语法糖替代.
回到正题, 上面函数其实就体现了 2). pthread线程api 优化 . 主要体现在 我用
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 来替代
pthread_detach(pthread_self());
把线程启动运行的设置, 移动到线程外边初始化中. 意思就是创建即拥有.
降低了线程控制代码的耦合性, 微量为线程业务代码提速了.
启动慢, 运行快. 或者说不认识的时候难认识, 熟悉后好相处其实更好. O(∩_∩)O哈哈~
正文 -_- 详细的设计
首先看核心结构, 每个线程对象
// 线程结构体, 每个线程一个信号量, 定点触发
struct thread {
struct thread * next; // 下一个线程对象
bool wait; // true 表示当前线程被挂起
pthread_t tid; // 当前线程id
pthread_cond_t cond; // 线程条件变量
};
线程启动对象是一个链表. wait表示当前线程挂起状态, 用于能够快速激活挂起的线程.
// 找到空闲的线程, 并返回起信号量
static pthread_cond_t * _threads_getcont(struct threads * pool) {
struct thread * head = pool->thrs;
while (head) {
if (head->wait)
return &head->cond;
head = head->next;
}
return NULL;
}
其中 struct threads 是所有线程对象的调度结构.
// 定义线程池(线程集)定义
struct threads {
size_t size; // 线程池大小, 最大线程结构体数量
size_t curr; // 当前线程池中总的线程数
size_t idle; // 当前线程池中空闲的线程数
pthread_mutex_t mutx; // 线程互斥量
struct thread * thrs; // 线程结构体对象集
struct job * head; // 线程任务链表的链头, 队列结构
struct job * tail; // 线程任务队列的表尾, 后插入后执行
};
任务job采用的是一个队列结构. 线程链表同时消耗这个生产者队列.
上面wait 设计体现了 3). 在解决惊群的基础上, 更进一步, 精度定位.
对于第四点 4). 增加了更多的安全性代码 我们的做法体现在pthread 线程的属性控制上.
// 设置线程属性, 设置线程 允许退出线程
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
设置开启线程取消状态, 并且支持异步取消.
// 构建线程, 构建完毕直接获取
if (pool->idle > ) {
pthread_cond_t * cond = _threads_getcont(pool);
// 先释放锁后发送信号激活线程, 速度快, 缺点丧失线程执行优先级
pthread_mutex_unlock(mutx);
// 发送给空闲的线程, 这个信号量一定存在
pthread_cond_signal(cond);
return;
}
定点发送信号, 精准的解决了惊群现象. 能够用空间换时间那就换, 但是不要浪费.
扯一点其它惊群, 例如在多进程中epoll中. fork 后 epoll -> accept 只有一个成功,多个失败.
解决方案也是有的.最简单就是忽略惊群错误, 但是性能有点影响.也可以通过均衡轮询文件描述符处理.
对于本线程池相关的详细说明, 可以看下面几个源文件和测试文件
说道最后, 改动的主要原因是以前那版太丑了, 看不惯. 觉得美是好的, 美是一种愉悦的感受~ _φ( °-°)/
为了美怎么办, 那就整呗~
后记 -_- 多留点记忆吧, 说不定就忘了
问题是难免的, 唯有打磨斟酌~
北方的故事 http://music.163.com/#/song?id=37782112
你羡慕我的心无旁骛, 我羡慕你的幸福生活
C 实现有追求的线程池 后续的更多相关文章
- C 实现有追求的线程池 探究
引言 线程池很普通的老话题,讨论的很多.深入的不多,也就那些基础库中才能见到这种精妙完备的技巧.而本文随大流 想深入简述一种高效控制性强的一种线程池实现. 先引入一个概念, 惊群. 简单举个例子. 春 ...
- 基于Linux/C++简单线程池的实现
我们知道Java语言对于多线程的支持十分丰富,JDK本身提供了很多性能优良的库,包括ThreadPoolExecutor和ScheduleThreadPoolExecutor等.C++11中的STL也 ...
- Vista 及后续版本的新线程池
在上一篇的博文中,说了下老版本的线程池,在Vista之后,微软重新设计了一套线程池机制,并引入一组新的线程池API,新版线程池相对于老版本的来说,它的可控性更高,它允许程序员自己定义线程池,并规定线程 ...
- Java线程池实现原理及其在美团业务中的实践
本文转载自Java线程池实现原理及其在美团业务中的实践 导语 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供 ...
- spring定时任务ThreadPoolTaskScheduler使用注意事项之线程池大小
背景 最近小伙伴解决了一个工单,描述为"手工推送案件无法推,提示token失效",当前工单状态为待关闭,解决方案为"东软接口不稳定造成的,东软的接口恢复正常后,问题解决& ...
- Java线程池实现原理及其在美团业务中的实践(转)
转自美团技术团队:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html 随着计算机行业的飞速发展,摩尔定律逐 ...
- jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一)
jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一) 线程池介绍 在日常开发中经常会遇到需要使用其它线程将大量任务异步处理的场景(异步化以及提升系统的吞吐量),而在 ...
- 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
- javase-->多线程--线程池
java的线程池理解 在面向对象编程中,对象创建和销毁是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. ...
随机推荐
- poj1456——Supermarket
Supermarket Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 14656 Accepted: 6656 Desc ...
- [51nod1325]两棵树的问题
description 题面 solution 点分治+最小割. 点分必选的重心,再在树上dfs判交,转化为最大权闭合子图. 可以做\(k\)棵树的情况. code #include<iostr ...
- POJ3304:Segments——题解
http://poj.org/problem?id=3304 题目大意:给n条线段,求是否存在一条直线,将所有线段投影到上面,使得所有投影至少交于一点. ——————————————————————— ...
- [Leetcode] gas station 气站
There are N gas stations along a circular route, where the amount of gas at station i isgas[i]. You ...
- 洛谷 P3119 [USACO15JAN]草鉴定Grass Cownoisseur 解题报告
P3119 [USACO15JAN]草鉴定Grass Cownoisseur 题目描述 约翰有\(n\)块草场,编号1到\(n\),这些草场由若干条单行道相连.奶牛贝西是美味牧草的鉴赏家,她想到达尽可 ...
- mybatis 根据id批量删除的两种方法
原文:https://blog.csdn.net/qq_40010745/article/details/81032218 mybatis 根据id批量删除的两种方法 第一种,直接传递给mappe ...
- 第九章 C99可变长数组VLA详解
C90及C++的数组对象定义是静态联编的,在编译期就必须给定对象的完整信息.但在程序设计过程中,我们常常遇到需要根据上下文环境来定义数组的情况,在运行期才能确知数组的长度.对于这种情况,C90及C++ ...
- CORS解决跨域访问问题
简言之,CORS就是为了让AJAX可以实现可控的跨域访问而生的. Tomcat下的配置 下载cors-filter-1.7.jar,java-property-utils-1.9.jar [下载 ...
- C++智能指针 auto_ptr
C++智能指针 auto_ptr auto_ptr 是一个轻量级的智能指针, 定义于 memory (非memory.h)中, 命名空间为 std. auto_ptr 适合用来管理生命周期比较短或者不 ...
- FreeRTOS - 中断使用注意
原文地址:http://www.cnblogs.com/god-of-death/p/6886823.html 注意点: 1.首先要将中断的嵌套全部设置为抢占优先级. 2.将freertos系统内核中 ...