C++ timer realizes timing tasks

1.1, the realization of a single timing task

There are several timers in boost's asio library, the old one is deadline_timer, and there are three high_resolution_timer, steady_timer and system_timer that can be used with C++11 chrono. Deadline_timer is a timer provided by the early version of asio. Using the boost.date_time library to provide time support, deadline_timer will be gradually eliminated.

To perform a task only after a fixed time, you can use the steady_timer timer provided by the asio library in boost. The usage of the timer is relatively simple, basically divided into three steps. Create  io_service  , create timer and set the waiting time, call wait or async_wait to wait.

Among them, wait is synchronous waiting, and async_wait is asynchronous waiting, and you need to give it a callback.

The specific implementation is as follows:

#include <iostream>
#include <boost/asio/io_service.hpp>
#include <boost/asio/steady_timer.hpp>
#include <thread>

void executeMission()
{
    std::cout<<"excute mission"<<std::endl;
}

int main()
{
    boost::asio::io_context io_ctx;
    boost::asio::steady_timer asio_steady_timer{io_ctx,
                std::chrono::seconds{3}};
    asio_steady_timer.async_wait([](const boost::system::error_code &ec)
        {std::cout<<"excute mission"<<std::endl;});
    //asio_steady_timer.async_wait(&executeMission);
    std::cout<<"start io_service"<<std::endl;

    io_ctx.run();

    return 0;
}

1.2, perform a fixed number of timing tasks

In order to achieve repeated timing, the expiration time of steady_timer needs to be modified in the callback function, so the steady_timer must be passed to the callback function:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/bind.hpp>
int count = 0;

void executeMission(const boost::system::error_code& e, boost::asio::steady_timer *t)
{
    std::cout << "execute mission" << std::endl;
    if(count < 10) {
        ++count;
        t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
        t->async_wait(boost::bind(executeMission, boost::asio::placeholders::error, t));
    }
}

int main()
{
    boost::asio::io_context io_ctx;
    boost::asio::steady_timer t(io_ctx, boost::asio::chrono::seconds(1));
    t.async_wait(boost::bind(executeMission, boost::asio::placeholders::error, &t));
    io_ctx.run();
    std::cout << "time: " << count << std::endl;
    return 0;
}

Encapsulate with class:

#include <iostream>
#include <chrono>
#include <thread>
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/system_timer.hpp>


class printer {
    private:
        boost::asio::io_context io_;
        boost::asio::steady_timer timer_;
        int count_;
        void print() {
            if (count_ < 500) {
                std::cout << count_ << "\n";
                ++count_;

                timer_.expires_from_now(std::chrono::milliseconds (50));
                timer_.async_wait(std::bind(&printer::print, this));
            }
            else
            {
                std::cout << "Final count is " << count_ << "\n";
                delete this;
            }
        }
        void run() {
            timer_.expires_from_now(std::chrono::milliseconds (50));
            timer_.async_wait(std::bind(&printer::print, this));
            io_.run();
        }
        printer()
            : timer_(io_),
              count_(0) {

        }
        ~printer() {

        }

    public:

        static printer* Create(){
            return new printer;
        }

        void start() {
            std::thread t;
            t = std::thread(std::mem_fn(&printer::run), this);
            t.detach();
        }
};
void foo()
{
    printer *p = printer::Create();
    p->start();
}
int main() {
    foo();
    std::cin.get();
    return 0;
}

Output:

....
490
491
492
493
494
495
496
497
498
499
Final count is 500

1.3. Processing timing callbacks in multi-threaded programs (multi-threaded processing multiple timing tasks)
Calling io_context::run() by one thread causes the callback function to not run concurrently. To avoid this limitation, a straightforward method is to use the thread pool to call io_context::run(). However, in order to be able to handle callbacks concurrently, a method is needed to synchronize callbacks' access to shared non-thread-safe resources.

The same io_context can be used by multiple timers at the same time

Example 1:

#include <iostream>
#include <chrono>
#include <thread>
#include <boost/asio.hpp>


#include <boost/asio/high_resolution_timer.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/system_timer.hpp>


class printer2 {
    private:
        boost::asio::steady_timer timer_;
        int count_;
        void print() {
            if (count_ < 10) {
                std::cout << count_ << "\n";
                ++count_;

                timer_.expires_from_now(std::chrono::milliseconds (500));
                timer_.async_wait(std::bind(&printer2::print, this));
            }
            else
            {
                std::cout << "Final count is " << count_ << "\n";
                delete this;
            }
        }
        printer2(boost::asio::io_context &io)
            : timer_(io,std::chrono::milliseconds (500)),
              count_(0) {
                timer_.async_wait(std::bind(&printer2::print, this));

        }
        ~printer2() {

        }

    public:

        static printer2* Create(boost::asio::io_context &io){
            return new printer2(io);
        }

};

int main() {
    boost::asio::io_context io;
    printer2::Create(io);
    printer2::Create(io);
    printer2::Create(io);
    printer2::Create(io);
    io.run();
    //boost::thread t1(boost::bind(&boost::asio::io_context::run, &io));
    std::cin.get();
    return 0;
}

Output;

......
7
7
7
7
8
8
8
8
9
9
9
9
Final count is 10
Final count is 10
Final count is 10
Final count is 10

Example 2:

#include <iostream>
#include <chrono>
#include <boost/thread.hpp>
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/system_timer.hpp>

class Timer
{
    public:
        Timer(boost::asio::io_context& io_ctx, boost::asio::io_context::strand& strand_1, unsigned int timeout_, unsigned int id_): id(id_), count(0), timeout(timeout_), t(io_ctx, boost::asio::chrono::milliseconds(timeout_)), strand_(strand_1)
    {
        t.async_wait(boost::asio::bind_executor(strand_, boost::bind(&Timer::OnTimerCallBack, this)));
    }
    private:
        void OnTimerCallBack()
        {
            if(count < 10) {
                ++count;
                std::cout << " Id:" << id << " Count:" << count << std::endl;
                t.expires_at(t.expiry() + boost::asio::chrono::milliseconds(timeout));
                t.async_wait(boost::asio::bind_executor(strand_, boost::bind(&Timer::OnTimerCallBack, this)));
            }
        }
    private:
        unsigned int id;
        unsigned int count;
        unsigned int timeout;
        boost::asio::steady_timer t;
        boost::asio::io_context::strand& strand_;
};

int main()
{
    boost::asio::io_context io_ctx;
    boost::asio::io_context::strand strand_(io_ctx);
    Timer timer1(io_ctx, strand_, 1000, 1);
    Timer timer2(io_ctx, strand_, 1000, 2);
    Timer timer3(io_ctx, strand_, 1000, 3);
    boost::thread t1(boost::bind(&boost::asio::io_context::run, &io_ctx));
    boost::thread t2(boost::bind(&boost::asio::io_context::run, &io_ctx));
    t1.join();
    t2.join();

    return 0;
}

1.4, cyclically execute timing tasks

//boost::posix_time::to_simple_string函数需要这两个头文件
#include <boost/date_time.hpp>
#include <boost/date_time/posix_time/ptime.hpp>
//使用boost.chrono代替std.chrono,
#define BOOST_ASIO_DISABLE_STD_CHRONO
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/placeholders.hpp>
#include <boost/thread.hpp>

class Timer
{
public:
    Timer() :work_(io_), timer_(io_){}
public:
    boost::thread_group thgp_;
    boost::asio::io_context io_;
    boost::asio::io_context::work work_;
    boost::asio::steady_timer timer_;
public:
    void init()
    {
        boost::system::error_code errCode;
        thgp_.create_thread(boost::bind(&boost::asio::io_service::run, boost::ref(io_), errCode));
        timer_.expires_from_now(boost::chrono::milliseconds(1000)); //设置过期时间长度
        timer_.async_wait(boost::bind(&Timer::excuteMission, this, boost::asio::placeholders::error));//异步等待
        std::cout << "initialize:" << localTime() << std::endl;
        //由Console可知, 函数立即返回了, 定时器的expires_from_now是由完成端口处理的
    }
    void stop()
    {
        timer_.cancel();  // 取消所有handler
        work_.~work();
        thgp_.join_all();
        std::cout << "Stop:" << localTime() << std::endl;
    }
    static std::string localTime()
    {
        return boost::posix_time::to_simple_string(boost::posix_time::microsec_clock::local_time());
    }

    void excuteMission(const boost::system::error_code& ec)
    {
        std::cout<<"mission to print time:"<<localTime().c_str()<<" ErrorValue:"<<ec.value()<<" ErrorCode:"<<ec.message().c_str()<<std::endl;
        timer_.expires_from_now(boost::chrono::milliseconds(1000));
        timer_.async_wait(boost::bind(&Timer::excuteMission, boost::ref(*this), _1));
#if 0
        timer_.async_wait(boost::bind(&Timer::excuteMission, this, _1));
        timer_.async_wait(boost::bind(&Timer::excuteMission, this, boost::asio::placeholders::error));
#endif
    }
};

int main(int argc, char** argv)
{
    Timer t;
    t.init();
    while(true)
    {
        std::cout<<"execute other mission"<<std::endl;
        boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
    }
    t.stop();
    std::cout << "press ENTER to exit..." << std::endl;
    //    std::cin.sync();
    return 0;
}

2. Implement a timer to perform timing tasks

The timer generally supports single thread, and the general usage method is shown in the following code. If you need multi-threading, the author generally uses a simple method: the multi-threaded business thread does not contain the timer manager, a separate thread is used to manage all the timers, and when the time is triggered, the timer is delivered to the business thread Message.

2.1. Minimal heap implementation

The header file min_heap.h

#ifndef TIMERMANAGER_H
#define TIMERMANAGER_H

#include <vector>
#include <boost/function.hpp>
namespace MinHeap {

class TimerManager;

class Timer
{
public:
    enum TimerType { ONCE, CIRCLE };

    Timer(TimerManager& manager);
    ~Timer();

    template<typename Fun>
    void Start(Fun fun, unsigned interval, TimerType timeType = CIRCLE);
    void Stop();

private:
    void OnTimer(unsigned long long now);

private:
    friend class TimerManager;
    TimerManager& manager_;
    TimerType timerType_;
    boost::function<void(void)> timerFun_;
    unsigned interval_;
    unsigned long long expires_;

    size_t heapIndex_;
};

class TimerManager
{
public:
    static unsigned long long GetCurrentMillisecs();
    void DetectTimers();
private:
    friend class Timer;
    void AddTimer(Timer* timer);
    void RemoveTimer(Timer* timer);

    void UpHeap(size_t index);
    void DownHeap(size_t index);
    void SwapHeap(size_t, size_t index2);

private:
    struct HeapEntry
    {
        unsigned long long time;
        Timer* timer;
    };
    std::vector<HeapEntry> heap_;
};

template<typename Fun>
void Timer::Start(Fun fun, unsigned interval, TimerType timeType)
{
    Stop();
    interval_ = interval;
    timerFun_ = fun;
    timerType_ = timeType;
    expires_ = interval_ + TimerManager::GetCurrentMillisecs();
    manager_.AddTimer(this);
}

}
#endif // TIMERMANAGER_H

Source file min_heap.cpp

#include "min_heap.h"
#define _CRT_SECURE_NO_WARNINGS
# include <sys/time.h>

namespace MinHeap {

Timer::Timer(TimerManager& manager)
    : manager_(manager)
    , heapIndex_(-1)
{
}

Timer::~Timer()
{
    Stop();
}

void Timer::Stop()
{
    if (heapIndex_ != -1)
    {
        manager_.RemoveTimer(this);
        heapIndex_ = -1;
    }
}

void Timer::OnTimer(unsigned long long now)
{
    if (timerType_ == Timer::CIRCLE)
    {
        expires_ = interval_ + now;
        manager_.AddTimer(this);
    }
    else
    {
        heapIndex_ = -1;
    }
    timerFun_();
}

// TimerManager
void TimerManager::AddTimer(Timer* timer)
{
    timer->heapIndex_ = heap_.size();
    HeapEntry entry = { timer->expires_, timer };
    heap_.push_back(entry);
    UpHeap(heap_.size() - 1);
}

void TimerManager::RemoveTimer(Timer* timer)
{
    size_t index = timer->heapIndex_;
    if (!heap_.empty() && index < heap_.size())
    {
        if (index == heap_.size() - 1)
        {
            heap_.pop_back();
        }
        else
        {
            SwapHeap(index, heap_.size() - 1);
            heap_.pop_back();
            size_t parent = (index - 1) / 2;
            if (index > 0 && heap_[index].time < heap_[parent].time)
                UpHeap(index);
            else
                DownHeap(index);
        }
    }
}

void TimerManager::DetectTimers()
{
    unsigned long long now = GetCurrentMillisecs();

    while (!heap_.empty() && heap_[0].time <= now)
    {
        Timer* timer = heap_[0].timer;
        RemoveTimer(timer);
        timer->OnTimer(now);
    }
}

void TimerManager::UpHeap(size_t index)
{
    size_t parent = (index - 1) / 2;
    while (index > 0 && heap_[index].time < heap_[parent].time)
    {
        SwapHeap(index, parent);
        index = parent;
        parent = (index - 1) / 2;
    }
}

void TimerManager::DownHeap(size_t index)
{
    size_t child = index * 2 + 1;
    while (child < heap_.size())
    {
        size_t minChild = (child + 1 == heap_.size() || heap_[child].time < heap_[child + 1].time)
            ? child : child + 1;
        if (heap_[index].time < heap_[minChild].time)
            break;
        SwapHeap(index, minChild);
        index = minChild;
        child = index * 2 + 1;
    }
}

void TimerManager::SwapHeap(size_t index1, size_t index2)
{
    HeapEntry tmp = heap_[index1];
    heap_[index1] = heap_[index2];
    heap_[index2] = tmp;
    heap_[index1].timer->heapIndex_ = index1;
    heap_[index2].timer->heapIndex_ = index2;
}


unsigned long long TimerManager::GetCurrentMillisecs()
{
#ifdef _MSC_VER
    _timeb timebuffer;
    _ftime(&timebuffer);
    unsigned long long ret = timebuffer.time;
    ret = ret * 1000 + timebuffer.millitm;
    return ret;
#else
    timeval tv;
    ::gettimeofday(&tv, 0);
    unsigned long long ret = tv.tv_sec;
    return ret * 1000 + tv.tv_usec / 1000;
#endif
}

}

2.2. Time wheel implementation

The header file timer_wheel.h

#ifndef TIMERWHEEL_H
#define TIMERWHEEL_H
#pragma once
#include <list>
#include <vector>
#include <boost/function.hpp>

namespace TimerWheel{

class TimerManager;

class Timer
{
public:
    enum TimerType {ONCE, CIRCLE};

    Timer(TimerManager& manager);
    ~Timer();

    template<typename Fun>
    void Start(Fun fun, unsigned interval, TimerType timeType = CIRCLE);
    void Stop();

private:
    void OnTimer(unsigned long long now);

private:
    friend class TimerManager;

    TimerManager& manager_;
    TimerType timerType_;
    boost::function<void(void)> timerFun_;
    unsigned interval_;
    unsigned long long expires_;

    int vecIndex_;
    std::list<Timer*>::iterator itr_;
};

class TimerManager
{
public:
    TimerManager();

    static unsigned long long GetCurrentMillisecs();
    void DetectTimers();

private:
    friend class Timer;
    void AddTimer(Timer* timer);
    void RemoveTimer(Timer* timer);

    int Cascade(int offset, int index);

private:
    typedef std::list<Timer*> TimeList;
    std::vector<TimeList> tvec_;
    unsigned long long checkTime_;
};

template<typename Fun>
inline void Timer::Start(Fun fun, unsigned interval, TimerType timeType)
{
    Stop();
    interval_ = interval;
    timerFun_ = fun;
    timerType_ = timeType;
    expires_ = interval_ + TimerManager::GetCurrentMillisecs();
    manager_.AddTimer(this);
}
}
#endif // TIMERWHEEL_H

Source file timer_wheel.cpp

#include "timer_wheel.h"
#define _CRT_SECURE_NO_WARNINGS
# include <sys/time.h>
#define TVN_BITS 6
#define TVR_BITS 8
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)
#define OFFSET(N) (TVR_SIZE + (N) *TVN_SIZE)
#define INDEX(V, N) ((V >> (TVR_BITS + (N) *TVN_BITS)) & TVN_MASK)

namespace TimerWheel{
Timer::Timer(TimerManager& manager)
    : manager_(manager)
    , vecIndex_(-1)
{
}

Timer::~Timer()
{
    Stop();
}

void Timer::Stop()
{
    if (vecIndex_ != -1)
    {
        manager_.RemoveTimer(this);
        vecIndex_ = -1;
    }
}

void Timer::OnTimer(unsigned long long now)
{
    if (timerType_ == Timer::CIRCLE)
    {
        expires_ = interval_ + now;
        manager_.AddTimer(this);
    }
    else
    {
        vecIndex_ = -1;
    }
    timerFun_();
}

// TimerManager
TimerManager::TimerManager()
{
    tvec_.resize(TVR_SIZE + 4 * TVN_SIZE);
    checkTime_ = GetCurrentMillisecs();
}

void TimerManager::AddTimer(Timer* timer)
{
    unsigned long long expires = timer->expires_;
    unsigned long long idx = expires - checkTime_;

    if (idx < TVR_SIZE)
    {
        timer->vecIndex_ = expires & TVR_MASK;
    }
    else if (idx < 1 << (TVR_BITS + TVN_BITS))
    {
        timer->vecIndex_ = OFFSET(0) + INDEX(expires, 0);
    }
    else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS))
    {
        timer->vecIndex_ = OFFSET(1) + INDEX(expires, 1);
    }
    else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS))
    {
        timer->vecIndex_ = OFFSET(2) + INDEX(expires, 2);
    }
    else if ((long long) idx < 0)
    {
        timer->vecIndex_ = checkTime_ & TVR_MASK;
    }
    else
    {
        if (idx > 0xffffffffUL)
        {
            idx = 0xffffffffUL;
            expires = idx + checkTime_;
        }
        timer->vecIndex_ = OFFSET(3) + INDEX(expires, 3);
    }

    TimeList& tlist = tvec_[timer->vecIndex_];
    tlist.push_back(timer);
    timer->itr_ = tlist.end();
    --timer->itr_;
}

void TimerManager::RemoveTimer(Timer* timer)
{
    TimeList& tlist = tvec_[timer->vecIndex_];
    tlist.erase(timer->itr_);
}

void TimerManager::DetectTimers()
{
    unsigned long long now = GetCurrentMillisecs();
    while (checkTime_ <= now)
    {
        int index = checkTime_ & TVR_MASK;
        if (!index &&
            !Cascade(OFFSET(0), INDEX(checkTime_, 0)) &&
            !Cascade(OFFSET(1), INDEX(checkTime_, 1)) &&
            !Cascade(OFFSET(2), INDEX(checkTime_, 2)))
        {
            Cascade(OFFSET(3), INDEX(checkTime_, 3));
        }
        ++checkTime_;

        TimeList& tlist = tvec_[index];
        TimeList temp;
        temp.splice(temp.end(), tlist);
        for (TimeList::iterator itr = temp.begin(); itr != temp.end(); ++itr)
        {
            (*itr)->OnTimer(now);
        }
    }
}

int TimerManager::Cascade(int offset, int index)
{
    TimeList& tlist = tvec_[offset + index];
    TimeList temp;
    temp.splice(temp.end(), tlist);

    for (TimeList::iterator itr = temp.begin(); itr != temp.end(); ++itr)
    {
        AddTimer(*itr);
    }

    return index;
}

unsigned long long TimerManager::GetCurrentMillisecs()
{
#ifdef _MSC_VER
    _timeb timebuffer;
    _ftime(&timebuffer);
    unsigned long long ret = timebuffer.time;
    ret = ret * 1000 + timebuffer.millitm;
    return ret;
#else
    timeval tv;
    ::gettimeofday(&tv, 0);
    unsigned long long ret = tv.tv_sec;
    return ret * 1000 + tv.tv_usec / 1000;
#endif
}
}

2.3 Timer application

main.cpp

#include <iostream>
#include <thread>
#include <chrono>
#include "timer_wheel.h"
#include "min_heap.h"
#include <chrono>

void TimerHandler()
{

    std::chrono::steady_clock::duration d =
            std::chrono::steady_clock::now().time_since_epoch();

    std::chrono::microseconds mic = std::chrono::duration_cast<std::chrono::microseconds>(d);

    std::cout << "Timer:"<<mic.count() << std::endl;
}

typedef void (*Function)();

int main()
{
//    MinHeap::TimerManager tm;
//    MinHeap::Timer t(tm);
    TimerWheel::TimerManager tm;
    TimerWheel::Timer t(tm);
    t.Start(&TimerHandler, 1000);
    while (true)
    {
        tm.DetectTimers();
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }

    std::cin.get();
    return 0;
}

Output:

Timer:6674738409
Timer:6675739056
Timer:6676739783
Timer:6677740959
Timer:6678746422
Timer:6679749721
Timer:6680751169
Timer:6681754799
Timer:6682754395
Timer:6683762516
^CPress <RETURN> to close this window...

reference:

https://www.cnblogs.com/lyqf365/p/4285166.html

https://zhuanlan.zhihu.com/p/31906251

Guess you like

Origin blog.csdn.net/sunlin972913894/article/details/106169559