Application instance of collaboration: timer expiration event response
order
. Its event response system has always impressed me, especially that every event can be delayed by waiting function. Yesterday, I saw the cooperation process and wrote a simple timer response mechanism.
1. Time scheduling center
2. Event handling based on cooperation process
.
.
Of course, in order to solve this problem, a thread pool technology is proposed. However, there is still a problem that a lot of time is wasted on thread switching when the thread exceeds a certain threshold.
based on the above considerations, I decided to execute event response processing with the cooperation procedure, and also realized the waiting function (no need to block the thread, only need to reschedule after a period of time).
.
Protocol return value type promise U type, used to interact with the protocol
class co_call { public: class promise_type { public: using value_type = size_t; public: promise_type &get_return_object() { return *this; } auto initial_suspend() { return std::experimental::suspend_always{}; } auto final_suspend() { // suspend it to save the coroutine handle return std::experimental::suspend_always{}; } void yield_value(value_type _Value) { _CurrentValue = _Value; } auto return_value(value_type _Value) { _CurrentValue = _Value; return std::experimental::suspend_always{}; // dont suspend it } value_type operator *(void) const noexcept { return _CurrentValue; } public: value_type _CurrentValue; }; using value_type = promise_type::value_type; public: explicit co_call(promise_type &_Prom) : _Coro(::std::experimental::coroutine_handle<promise_type>::from_promise(_Prom)), _Value(*_Prom) { } co_call() = default; co_call(co_call const &) = delete; co_call(co_call &&_Right) : _Coro(_Right._Coro), _Value(_Right._Value) { _Right._Coro = nullptr; _Right._Value = 0; } ~co_call() { if (_Coro) { _Coro.destroy(); } } public: _NODISCARD value_type resume() { if (_Coro) { _Coro.resume(); _Value = *_Coro.promise(); if (_Coro.done() || (_Value == 0)) { _Coro.destroy(); _Coro = 0; _Value = 0; return _Value; } } return _Value; } public: co_call &operator=(co_call const &) = delete; co_call &operator=(co_call &&_Right) { if (this != _STD addressof(_Right)) { _Coro = _Right._Coro; _Right._Coro = nullptr; _Value = _Right._Value; _Right._Value = 0; } return *this; } operator bool(void) { return (_Coro != 0); } private: ::std::experimental::coroutine_handle<promise_type> _Coro = nullptr; value_type _Value = 0; };
Timer object, which holds the process handle. If the event has a waiting function, it will suspend first and return a waiting time. It will be scheduled again after a period of time. Otherwise, it will directly return 0
class _XTimer { public: public: _XTimer() :task(0), id(0), handle() { } _XTimer(int c,int i) :task(c), id(i), handle() { } ~_XTimer() { } public: co_call on_event(size_t now) { std::cout << "timer ID: " << id << ", BEGIN AT: " << now << ", WAIT: " << task << std::endl; // Suspend for a period of time, give up the time slice, and execute other core _COT_WAIT(task); std::cout << "timer ID: " << id << ", TASK DONE! (AT:" << now << ") " << std::endl; _COT_NORET(); } public: int task; int expires; int id; co_call handle; };
Here's the whole code
#include <iostream> #include <list> #include <cassert> #include <algorithm> #include <mutex> #include <experimental/coroutine> #include <experimental/resumable> #include <experimental/generator> #include <Windows.h> #include <thread> #include <future> using namespace std; #define _COT_WAIT(x) (co_yield (x)) #define _COT_NORET() co_return (0) class co_call { public: class promise_type { public: using value_type = size_t; public: promise_type &get_return_object() { return *this; } auto initial_suspend() { return std::experimental::suspend_always{}; } auto final_suspend() { // suspend it to save the coroutine handle return std::experimental::suspend_always{}; } void yield_value(value_type _Value) { _CurrentValue = _Value; } auto return_value(value_type _Value) { _CurrentValue = _Value; return std::experimental::suspend_always{}; // dont suspend it } value_type operator *(void) const noexcept { return _CurrentValue; } public: value_type _CurrentValue; }; using value_type = promise_type::value_type; public: explicit co_call(promise_type &_Prom) : _Coro(::std::experimental::coroutine_handle<promise_type>::from_promise(_Prom)), _Value(*_Prom) { } co_call() = default; co_call(co_call const &) = delete; co_call(co_call &&_Right) : _Coro(_Right._Coro), _Value(_Right._Value) { _Right._Coro = nullptr; _Right._Value = 0; } ~co_call() { if (_Coro) { _Coro.destroy(); } } public: _NODISCARD value_type resume() { if (_Coro) { _Coro.resume(); _Value = *_Coro.promise(); if (_Coro.done() || (_Value == 0)) { _Coro.destroy(); _Coro = 0; _Value = 0; return _Value; } } return _Value; } public: co_call &operator=(co_call const &) = delete; co_call &operator=(co_call &&_Right) { if (this != _STD addressof(_Right)) { _Coro = _Right._Coro; _Right._Coro = nullptr; _Value = _Right._Value; _Right._Value = 0; } return *this; } operator bool(void) { return (_Coro != 0); } private: ::std::experimental::coroutine_handle<promise_type> _Coro = nullptr; value_type _Value = 0; }; class _XTimer { public: public: _XTimer() :task(0), id(0), handle() { } _XTimer(int c,int i) :task(c), id(i), handle() { } ~_XTimer() { } public: co_call on_event(size_t now) { std::cout << "timer ID: " << id << ", BEGIN AT: " << now << ", WAIT: " << task << std::endl; // Suspend for a period of time, release the time slice, and execute other core _COT_WAIT(task); std::cout << "timer ID: " << id << ", TASK DONE! (AT:" << now << ") " << std::endl; _COT_NORET(); } public: int task; int expires; int id; co_call handle; }; class _XTimeDisp { public: using _Cont_ty = ::std::list<class _XTimer*>; static constexpr int _TVN_BITS = 4; static constexpr int _TVR_BITS = 6; static constexpr int _TVN_SIZE = 1 << _TVN_BITS; static constexpr int _TVR_SIZE = 1 << _TVR_BITS; static constexpr int _TVN_MASK = _TVN_SIZE - 1; static constexpr int _TVR_MASK = _TVR_SIZE - 1; public: _XTimeDisp() :_tq_mutex(), tvec(), _sign_mutex(), _run_thread(), _sign(), _thread_state(0) { } ~_XTimeDisp() { } public: static constexpr int _INDEX(int expires, int n) { return ((expires >> (_TVR_BITS + n * _TVN_BITS)) & _TVN_MASK); } static constexpr int _OFFSET(int n) { return (_TVR_SIZE + n*_TVN_SIZE); } public: void add(_XTimer* timer) { unsigned long long expires = timer->expires; unsigned long long index = expires - _Check_time; unsigned int _VecIndex = 0; if (index < _TVR_SIZE) // tvec_1 { tvec_1[expires & _TVR_MASK].push_back(timer); } else if (index < (1 << (_TVR_BITS + 1 * _TVN_BITS))) // tvec_2 { tvec_2[_INDEX(expires, 0)].push_back(timer); } else if (index <= 0) // Exception handling, treated as an expiring timer { tvec_1[_Check_time & _TVR_MASK].push_back(timer); } else { if (index > 0xFFFFFFFFUL) { index = 0xFFFFFFFFUL; expires = index + _Check_time; } tvec_1[_INDEX(expires, 1)].push_back(timer); } } int cascade(int offset, int index) { ::std::unique_lock<::std::recursive_mutex> _lock(_tq_mutex); _Cont_ty& list = tvec[offset + index]; _Cont_ty empty; ::std::swap(empty, list); for (auto it = empty.begin(); it != empty.end(); ++it) { this->add(*it); } return index; } void tick(size_t _Now) { ::std::unique_lock<::std::recursive_mutex> _lock(_tq_mutex); while (_Check_time <= _Now) { int index = _Check_time & _TVR_MASK; if (!index && // tv1 !cascade(_OFFSET(0), _INDEX(_Check_time, 0))) // tv2 { cascade(_OFFSET(1), _INDEX(_Check_time, 1)); // tv3 } ++_Check_time; _Cont_ty& list = tvec[index]; _Cont_ty empty; ::std::swap(empty, list); for (auto it = empty.begin(); it != empty.end(); ++it) { // If a handle indicates that it is suspended, continue if ((*it)->handle) { auto ret = (*it)->handle.resume(); if (ret != 0) { (*it)->expires += ret; add(*it); } } else { // As first execution auto res = (*it)->on_event(_Now); auto ret = res.resume(); if (ret != 0) { (*it)->expires += ret; (*it)->handle = ::std::move(res); add(*it); } } } } } void run() { while (_thread_state == 1) { tick(_Check_time); ::std::unique_lock<::std::mutex> _nofity(_sign_mutex); _sign.wait(_nofity); } } void nofity(size_t now) { _Check_time = now; _sign.notify_one(); } void start(size_t time) { if (_thread_state) { return; } _Check_time = time; _thread_state = 1; _run_thread = ::std::thread(&_XTimeDisp::run, this); } void stop() { _thread_state = 0; _sign.notify_one(); if (_run_thread.joinable()) { _run_thread.join(); } } public: size_t _Check_time; union { class { public: _Cont_ty tvec_1[_TVR_SIZE]; _Cont_ty tvec_2[_TVN_SIZE]; _Cont_ty tvec_3[_TVN_SIZE]; }; _Cont_ty tvec[_TVR_SIZE + 2 * _TVN_SIZE]; }; ::std::recursive_mutex _tq_mutex; ::std::mutex _sign_mutex; ::std::condition_variable _sign; ::std::thread _run_thread; int _thread_state; }; using timer = _XTimer; using tdc = _XTimeDisp; void main() { timer t1, t2,t3,t4; t1.id = 1; t1.expires = 0; t1.task = 300; t2.id = 2; t2.task = 100; t2.expires = 3; t3.id = 3; t3.task = 50; t3.expires = 3; t4.id = 4; t4.task = 30; t4.expires = 88; tdc tm; tm._Check_time = 0; tm.add(&t1); tm.add(&t2); tm.add(&t3); tm.add(&t4); tm.start(0); int tbegin = 0; while (1) { ::std::this_thread::sleep_for(::std::chrono::milliseconds(10)); tm.nofity(tbegin); ++tbegin; } system("pause"); } #undef _COT_WAIT #undef _COT_NORET
Run screenshots
Due to no multithreaded Sleep blocking, CPU utilization is naturally high