首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在C++11中调度任务的定时器

在C++11中调度任务的定时器
EN

Code Review用户
提问于 2019-11-22 11:29:11
回答 1查看 1.9K关注 0票数 3

我制作了一个简单的Timer类,用于调度任务。在定义接口时,我遵循了java.util.Timer类的接口。这个类只使用C++11编写。

C++17版本提供这里

timer.hpp

代码语言:javascript
复制
#include <atomic>
#include <chrono>
#include <future>
#include <utility>

template<typename F, typename... Args>
inline auto invoke(F f, Args&&... args)
    -> decltype(std::ref(f)(std::forward<Args>(args)...)) {
    return std::ref(f)(std::forward<Args>(args)...);
}

class Timer {
public:
    using Timestamp = std::chrono::time_point<
        std::chrono::system_clock,
        std::chrono::microseconds
    >;

    using Delay = std::chrono::milliseconds;
    using Period = std::chrono::milliseconds;

public:
    Timer() = delete;
    Timer(std::atomic<bool>& is_running) : is_running_(is_running) {}
    Timer(Timer const& other) = default;
    Timer(Timer&& other) = default;

    Timer& operator=(Timer const& other) = default;
    Timer& operator=(Timer&& other) = default;

    ~Timer() = default;

    template <typename TimerTask, typename ... Args>
    std::future<void> schedule(TimerTask task, Timestamp const& time, Args... args)  {
        using namespace std::chrono;
        return std::async(std::launch::async, [=]() {
            while (time_point_cast<Timestamp::duration>(system_clock::now()) < time) {
                // wait for the time
            }
            invoke(task, args...);
        });
    }

    template <typename TimerTask, typename Period, typename ... Args>
    std::future<void> schedule(TimerTask task, Timestamp const& time, Period const& period, Args... args)  {
        using namespace std::chrono;
        return std::async(std::launch::async, [=]() {
            while (time_point_cast<Timestamp::duration>(system_clock::now()) < time) {
                // wait for the time
            }
            while (is_running_) {
                invoke(task, args...);
                std::this_thread::sleep_for(period);
            }
        });
    }

    template <typename TimerTask, typename ... Args>
    std::future<void> schedule(TimerTask task, Delay const& delay, Args... args)  {
        using namespace std::chrono;
        return std::async(std::launch::async, [=]() {
            auto start = system_clock::now();
            while (duration_cast<Delay>(system_clock::now() - start) < delay) {
                // wait for the delay
            }
            invoke(task, args...);
        });
    }

    template <typename TimerTask, typename ... Args>
    std::future<void> schedule(TimerTask task, Delay const& delay, Period const& period, Args... args)  {
        using namespace std::chrono;
        return std::async(std::launch::async, [=]() {
            auto start = system_clock::now();
            while (duration_cast<Delay>(system_clock::now() - start) < delay) {
                // wait for the delay
            }
            while (is_running_) {
                invoke(task, args...);
                std::this_thread::sleep_for(period);
            }
        });
    }

    void cancel() {
        is_running_ = false;
    }

private:
    std::atomic<bool>& is_running_;
};

main.cpp

代码语言:javascript
复制
#include <iostream>
#include "timer.hpp"

int main() {
    std::atomic<bool> is_running{true};
    Timer timer(std::ref(is_running));

    Timer::Timestamp ts = std::chrono::time_point_cast<Timer::Timestamp::duration>(
        std::chrono::system_clock::now()
    );
    ts = ts + std::chrono::milliseconds{10000};

    auto future = timer.schedule([] {
        std::cout << "." << std::endl;
    }, ts, Timer::Period{5000});

    std::cout << "... in main ..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds{20});
    std::cout << "... cancel ..." << std::endl;
    timer.cancel();

    return 0;
}

让我听听您对此实现、代码风格、性能、可能的改进或可能的缺陷等的看法。

您还可以注意到通过使用C++17甚至C++20可以简化或更改的所有内容。

EN

回答 1

Code Review用户

发布于 2019-12-09 10:04:06

您对std::ref的使用是不必要的(无论在main还是invoke中)。只有当您将参数传递给通常会丢弃引用限定符的模板化函数时,才需要使用它,但在使用它时,情况并非如此。

您正在存储对std::atomic<bool>的外部引用。这在我看来非常奇怪,因为您强迫Timer的用户(1)创建它,(2)确保引用在Timer实例的生命周期内仍然有效。这无疑是一种恼怒,似乎没有任何好处。您已经拥有了cancel(),如果您想查看它是否正在运行,只需添加一个函数即可获得它。

只要让is_running_成为一个非引用的。(你在链接版本中这样做,但这里没有)

代码语言:javascript
复制
while (time_point_cast<Timestamp::duration>(system_clock::now()) < time) {
    // wait for the time
}

这将将线程锁定为100%的利用率,直到满足条件。这是非常糟糕的,因为它可能等待很长时间,无论用户想要什么。像这样的热循环可以通过添加微睡眠while (condition) { // sleep for a few milliseconds }来降温。

您已经在使用sleep_for(),但也有sleep_until()。因此,可以用以下方式替换while循环:

代码语言:javascript
复制
std::this_thread::sleep_until(time);

你的计时器会消耗更少的资源。(你更新了链接版本,所以我猜你已经知道了)

你应该重新考虑你的设计。

返回一个std::future<void>可能是个坏主意。std::future的析构函数(从std::async创建时)将阻止执行,直到函数结束。这将不允许触发和忘记调用,调用方必须等待函数被调用。在不同的情况下,您可能会使用这样的未来从异步函数返回一个值,但是这里没有这样做。

但是,不返回它有同样的问题,它只是将等待移动到schedule()中。你需要做的是举行一次std::vector of std::future<void>s会议。这将为Timer正在杂耍的所有功能提供视角,并迫使Timer的S破坏者等待任何未完成的功能完成。

然而,这也不是一个好主意;内部列表将在Timer的生命周期中不断增长,无法清除已完成的函数(至少不是干净的)。相反,让我们看看您的灵感来源:java.util.Timer文档说它是:

一种让线程在后台线程中调度任务以便将来执行的工具.与每个定时器对象对应的是一个单独的后台线程,用于依次执行定时器的所有任务。

你应该跟随他们的脚步。与其对异步函数杂耍多个句柄,您应该有一个std::thread来完成这项工作,保持一个需要调用的函数队列(可能是一个std::priority_queue ),可能还有一个用于同步从队列中添加和删除内容的std::mutex

这减少了Java,但是如果您想通过附加参数传递参数给schedule(),我建议将task参数移到最后。这使函数调用更接近参数列表中的参数。

除了停止整个计时器之外,您没有停止周期函数的机制。Java版本通过使用一个特殊的TimerTask类型来实现这一点,该类型用作添加cancel()函数的Callable的包装器。然而,对于C++,我建议实现取消令牌,用户可以使用这些令牌来停止周期性函数(如果他们愿意的话)。

您的代码非常容易检查:良好的代码风格、良好的名称、良好的using使用、良好的组织结构。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/232791

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档