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 @   zjh6  阅读(19)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示