C++的偷工

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

偷工线程池

实现思路

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

    极 弹(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;
            });
            断定(==线程池::错误类型::无错);
        }
    }
    断定(==计数);
}

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

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

posted @ 2022-04-03 17:54  zjh6  阅读(44)  评论(0)    收藏  举报  来源