实施"计划执行服务"

时间:2015-04-15 14:58:52

标签: multithreading c++11

我正在尝试编写简单的ScheduledExecutorService。我有一节课:

class ScheduledExecutor {
public:
    typedef size_t TaskID;
public:
    explicit ScheduledExecutor(size_t);
    ~ScheduledExecutor();
    ScheduledExecutor(ScheduledExecutor const &) = delete;
    ScheduledExecutor(ScheduledExecutor &&) = delete;
    ScheduledExecutor & operator = (ScheduledExecutor const &) = delete;
    ScheduledExecutor & operator = (ScheduledExecutor &&) = delete;

    template<typename Fn>
    TaskID ScheduleDelayedTask(
        Fn && fn, std::chrono::milliseconds delay = 0);

    template<typename Fn>
    TaskID SchedulePeriodicTask(
        Fn && fn, std::chrono::milliseconds,
        std::chrono::milliseconds period);
    .
    .
    .
private:
    std::vector< std::thread > workers;
    std::queue< std::function<void()> > tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

我实施了一些方法:

inline ScheduledExecutor::ScheduledExecutor(size_t threadPoolSize = 10) : stop(false) {

    for (size_t i = 0; i < threadPoolSize; ++i) {
        workers.emplace_back(
            [this]
        {
            for (;;) {
                std::function<void()> task;
                {
                    std::unique_lock<std::mutex> lock(this->queue_mutex);
                    this->condition.wait(lock,
                        [this]{ return this->stop || !this->tasks.empty(); });
                    if (this->stop && this->tasks.empty())
                        return;
                    task = std::move(this->tasks.front());
                    this->tasks.pop();
                }
                task();
            }
        });
    }
}

template<typename Fn>
ScheduledExecutor::TaskID ScheduledExecutor::ScheduleDelayedTask(
    Fn && fn, std::chrono::milliseconds delay) {
            {
                std::unique_lock<std::mutex> lock(queue_mutex);
                if (stop)
                    throw std::runtime_error("enqueue on stopped ThreadPool");
                tasks.push(std::function<void()>(fn));
            }
    condition.notify_one();
    return 0;
}

第一个问题是:&#34;如何将延迟添加到第一个方法( TaskID ScheduleDelayedTask )?我有一个想法是在匿名线程(在方法内部)中擦除此函数并在&#34; condition.notify_one(); &#34;之前将其休眠几秒钟。但这不是一个好方法(如果我们有10000个长延迟的任务?结果我们将有10000个线程无效)。 给我一个解决这个问题的好方法。

1 个答案:

答案 0 :(得分:1)

创建一个调度程序线程。当要启动的下一个任务是由于启动,或者某个条件变量被调用时,它会唤醒。

您维护一个优先级队列&#34;下一个要启动的任务是什么&#34;。当某些东西被添加到队列中时,如果它导致下一个任务更快,则通过调用条件变量来唤醒调度程序线程。

调度程序线程然后启动等待运行的任务,或者将它们置于准备运行状态(如果你有某种线程池)。

小心只运行有限的即用型任务子集,因为任务可以(理论上)阻止来自其他任务的消息(甚至忙碌等待),并注意到某些任务已被阻止,棘手。使一些任务匮乏(从不启动它们)会导致事情停滞不前。

一个挑战是MSVC 2013上std::chrono::steady_clock的解决方案非常糟糕。而非稳定的时钟可以使调度非常偶然。您希望跟踪下一个任务应该开始的时间,而不是下一个任务开始的时间。

重复任务很有趣,因为如果其中一个任务被延迟,你必须决定什么是开心的。重复是基于最后一次重复的开始时间吗?结束时间?如果最后一项任务在下一次重复发生之前没有开始怎么办?或者甚至还没完成?这些规则是微妙的,并且在客户代码中采用一种方式或另一种方式在不同情况下是合理和方便的。

依赖关系图是处理此类问题的一种方法,后续重复可以明确地依赖于之前的重复完成或开始。