Muduo network library source code reproduction notes (19): TimeQueue timer

Introduction to Muduo Network Library

Muduo is a modern C++ network library based on Reactor mode, author Chen Shuo. It uses a non-blocking IO model, based on event-driven and callbacks, natively supports multi-core and multi-threading, and is suitable for writing Linux server-side multi-threaded network applications.
The core code of muduo network library is only a few thousand lines. In the advanced stage of network programming technology learning, muduo is an open source library that is very worth learning. At present, I have just started to learn the source code of this network library, hoping to record this learning process. The source code of this web library has been published on GitHub, you can click here to read it. At present, the source code on Github has been rewritten in C++11 by the author, and the version I am studying does not use the C++11 version. However, the two are similar, and the core idea remains unchanged. Click here to see my source code . Starting from Note 17 to record the implementation process of muduo's net library. If you need to see the reproduction process of the base library , you can click here: The implementation process of muduo's base library . And the notes of the network library are here:
Muduo network library source code reproduction notes (17): EventLoop
Muduo network library source code reproduction notes (18): Key structure of Reactor

TimeQueue timer

Earlier we implemented a preliminary Reactor structure, this section will add the timer function. For example, the following code implements the following function: execute the task function after 10 seconds of timing, and execute the task2 function after every 2 seconds.

EventLoop loop;
loop.runAfter(10,boost::bind(task,"once"));
loop.runAfter(2,boost::bind(task2,"every 2"));
loop.loop()

In order to realize such a function, three classes of TimerId, Timer and TimerQueue are required. Only TimerId is visible to users. Let me explain these three classes.

1 TimeId class

TimeId is not difficult, it has only two attributes, one is its corresponding timer, and the other is the serial number of the timer.

class TimerId : public muduo::copyable
{
    
    
public:
	TimerId()
		:	timer_(NULL),
			sequence_(0)
	{
    
    
	}
	
	TimerId(Timer* timer,int64_t seq)
		:	timer_(timer),
			sequence_(seq)
	{
    
    
	}

	friend class TimerQueue;
private:
	Timer* timer_;
	int64_t sequence_;
};

2 Timer class

The Timer class is a timer class, and its related properties are in the comments. We will use the run function to perform the callback task. In TimerQueue, we bind each timer to the timer file descriptor timerfd, and the timer at that time will be taken out to perform tasks.

Timer(const TimerCallback& cb,Timestamp when,double interval)
		:	callback_(cb),
			expiration_(when),
			interval_(interval),
			repeat_(interval > 0),
			sequence_(s_numCreated_.incrementAndGet())
	{
    
    
	}

	void run() const
	{
    
    
		callback_();
	}

	Timestamp expiration() const
	{
    
    
		return expiration_;
	}

	bool repeat() const {
    
    return repeat_;}
	
	int64_t sequence() const {
    
    return sequence_;}

	void restart(Timestamp now);
	
	static int64_t numCreated() {
    
    return s_numCreated_.get();}
			
private:
	const TimerCallback callback_;  //要执行的任务
	Timestamp expiration_; //执行时间
	const double interval_; //重复间隔
	const bool repeat_;  //是否需要重复
	const int64_t sequence_; //序号
	
	static AtomicInt64 s_numCreated_; //定时器数目统计
};

3 TimerQueue class

TimerQueue is the key to realizing the timing function. It can add and remove timers, and take out timers that are up to date. Note the two members of TimerQueue, timerfd_ and timerfdChannel_. The former is a time file descriptor, and the latter is a Channel. Its function is to monitor the time of the timer and add Poller's Channels_ when the TimerQueue is initialized. The container where the timer is located is TimerList and ActiveTimerSet, which are roughly similar. Both are a set, but the element type of the former is pair<Timestamp,Timer*>, and its sorting method is in order of time; the latter is pair<Timer*,int64_t>, and the sorting method is according to the memory address of the timer .

class TimerQueue : boost::noncopyable
{
    
    
public:
	TimerQueue(EventLoop* loop);
	~TimerQueue();
	
	TimerId addTimer(const TimerCallback& cb,
				     Timestamp when,
					 double interval);
	
	void cancel(TimerId timerId);
private:
	typedef std::pair<Timestamp,Timer*> Entry;
	typedef std::set<Entry> TimerList;
	typedef std::pair<Timer*,int64_t> ActiveTimer;
	typedef std::set<ActiveTimer> ActiveTimerSet;

	void addTimerInLoop(Timer* timer);
	void cancelInLoop(TimerId timerId);
	//called when timerfd alarms
	void handleRead();
	std::vector<Entry> getExpired(Timestamp now);
	void reset(const std::vector<Entry>& expired,Timestamp now);
	
	bool insert(Timer* timer);

	EventLoop* loop_;
	const int timerfd_;
	Channel timerfdChannel_;
	TimerList timers_; //timers sorted by time

	ActiveTimerSet activeTimers_;
	bool callingExpiredTimers_;
	ActiveTimerSet cancelingTimers_;
};

3.1 getExpired

getExpired is used to fetch the timed timer and add it to TimerList and ActiveTimerSet. The place that this function needs to pay attention to is the sentry sentry. The first of its pair is now, and the second is the maximum memory address converted to Timer pointer . This ensures that the first end we get when using low_bound is always greater than now.

std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
    
    
	assert(timers_.size() == activeTimers_.size());
	std::vector<Entry> expired;
	Entry sentry(now,reinterpret_cast<Timer*>(UINTPTR_MAX));
	TimerList::iterator end = timers_.lower_bound(sentry);
	assert(end == timers_.end() || now < end->first);
	std::copy(timers_.begin(),end,back_inserter(expired));
	timers_.erase(timers_.begin(),end);

	for(std::vector<TimerQueue::Entry>::iterator it = expired.begin(); it != expired.end(); ++it)
	{
    
    
		ActiveTimer timer(it->second,it->second->sequence());
		size_t n = activeTimers_.erase(timer);
		assert(n == 1); (void)n;
	}
	assert(timers_.size() == activeTimers_.size());
	return expired;
}

Timing diagram

The sequence diagram is as follows. When we call the loop function, we get activeChannels, and then process each Channel. For the timer Channel, get the timer at the time and execute the task function it carries.
Insert picture description here

Guess you like

Origin blog.csdn.net/MoonWisher_liang/article/details/107495378