​偷工​​线程池解决了​​多队列​​线程池中"​​饥饿等待​​"问题,即有的​​线程队列​​中的​​任务​​很多,有的​​线程队列​​是​​空的​​,处于"​​饥饿等待​​"状态,​​偷工​​可把​​其它​​线程队列中的任务​​偷​​过来避免"​​饥饿​​",提高了线程池​​效率​​.

​偷工​​线程池

实现思路

之前线程池都是通过​​阻塞​​拿到队列中任务:

 (T&){
独锁 (_互斥锁);
_条件.等待(,[&](){!_队列.空的()||_停止;});
(_队列.空的()) ;
=移动(_队列.());_队列.();
;
}

​条件变量​​​会一直​​阻塞​​​直到拿到​​任务​​​,能否先​​非阻塞​​​从​​其它队列​​​中取​​任务​​​呢?如果​​取不到​​​,则​​阻塞​​取任务.

​改进​​有两个好处:

1,减少了​​阻塞​​等待,因为它按​​非阻塞​​方式从​​其它队列​​中取任务的,无需阻塞;

2,实现了​​偷工​​.

​c++​​​标准库提供了按​​非阻塞​​​取锁适合​​偷工​​​的​​std::try_to_lock_t​​.

接口设计

为了实现​​偷工​​​需要对​​队列​​​增加​​非阻塞​​取任务接口:

bool try_pop(T&item);

为了保持灵活,在不需要​​偷工​​​时,关闭​​偷工​​​功能,因此还需要增加​​try_pop_if​​​来控制是否需要​​偷工​​的接口:

bool try_pop_if(T& item, bool (*predict)(T&) = nullptr);

​线程池​​​需要​​入队和出队​​​逻辑,入队时先试​​非阻塞​​​,失败再随机选​​某个线程​​​队列;​​出队​​​时先试从​​其它​​​线程队列中​​偷​​​一个,如果​​偷​​​成功就执行​​偷​​​来任务,失败就​​阻塞​​等待取任务.

​​偷工​​核心代码:

内联 线程池::线程池(整32型 线程号, 允许偷工)
:_线程号(线程号?线程号:线程::硬件并行()),
_队列(_线程号),
_允许偷工(允许偷工),
_停止(){
工作者=[](整32型 标识){
当前=取当前();当前->第一=标识;
当前->第二=;
(){
工作项 工作者项={};
(_允许偷工){
//先试偷
( n=0;n<_线程号*2;++n){
(_队列[(标识+n)%_线程号].试弹如(
工作者项,
[](&){ .可偷;}))
;
}
}

//_enableWorkSteal为假,或偷失败,等待弹
//任务.
(!工作者项.函数&&!_队列[标识].(工作者项)){
;
}

(工作者项.函数){
工作者项.函数();
}
}
};

_线程.保留(_线程号);
( i=0;i<_线程号;++i){
_线程.原后(工作者,i);
}
}

内联 线程池::错误类型 线程池::按标识调度(函数<()>函数,
整32型 标识){
(空针==函数){
是无效错误项池;
}

(_停止){
有停止池错误;
}

(标识==-1){
(_允许偷工){
//先压非阻塞队列
工作项 工作者项{/*可偷=*/,函数};
( n=0;n<_线程号*2;++n){
(_队列.(n%_线程号).试压(工作者项))
无错;
}
}

标识=随机()%_线程号;
_队列[标识].(
工作项{/*可偷=*/_允许偷工,移动(函数)});
}
{
断定(标识<_线程号);
_队列[标识].(工作项{/*可偷=*/,移动(函数)});
}

无错;
}

测试代码:

#包含"线程池.基准.h"
#包含<原子>
#包含<c断定>

名字空间 简单异步::工具;

计数=500'000;
重复=10;

自动调度任务( 允许偷工){
原子<>=0;
{
线程池 tp(线程::硬件并行(),允许偷工);

( i=0;i<计数;++i){
[[也许未用]] =tp.按标识调度([i,&]{
++; x;
重复=重复+(重复*(随机()%5));
( n=0;n<重复;++n)
x=i+随机();
()x;
});
断定(==线程池::错误类型::无错);
}
}
断定(==计数);
}

线程池无偷工(基准::状态&状态){
([[也许未用]] &_:状态)
自动调度任务(/*允许偷工=*/);
}

线程池有偷工(基准::状态&状态){
([[也许未用]] &_:状态)
自动调度任务(/*允许偷工=*/);
}