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) < 0) \
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(0)
#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 > 0) {
pthread_cond_t * cond = _threads_getcont(pool);
// 先释放锁后发送信号激活线程, 速度快, 缺点丧失线程执行优先级
pthread_mutex_unlock(mutx);
// 发送给空闲的线程, 这个信号量一定存在
pthread_cond_signal(cond);
return;
}
定点发送信号, 精准的解决了惊群现象. 能够用空间换时间那就换, 但是不要浪费.
扯一点其它惊群, 例如在多进程中epoll中. fork 后 epoll -> accept 只有一个成功,多个失败.
解决方案也是有的.最简单就是忽略惊群错误, 但是性能有点影响.也可以通过均衡轮询文件描述符处理.
对于本线程池相关的详细说明, 可以看下面几个源文件和测试文件
scthreads.h
scthreads.c
test_scthreads.c
说道最后, 改动的主要原因是以前那版太丑了, 看不惯. 觉得美是好的, 美是一种愉悦的感受~ _φ( °-°)/
为了美怎么办, 那就整呗~
后记 -_- 多留点记忆吧, 说不定就忘了
问题是难免的, 唯有打磨斟酌~
北方的故事 http://music.163.com/#/song?id=37782112
你羡慕我的心无旁骛, 我羡慕你的幸福生活