muduo library net source code analysis III (timer)

Enables EventLoop to handle timer events

Timing function

Used to make the program wait for a period of time or schedule a scheduled task:

sleep

alarm usleep

nanosleep

clock_nanosleep

getitimer / setitimer

timer_create / timer_settime / timer_gettime / timer_delete

timerfd_create / timerfd_gettime / timerfd_settime chooses this method.

Why timerfd_create?

sleep / alarm / usleep may use the signal SIGALRM in its implementation. Processing signals in multithreaded programs is quite troublesome and should be avoided as much as possible.

nanosleep and clock_nanosleep is thread safe, but in non blocking network programming, you must not wait for a period of time by suspending the thread, and the program will lose response. The correct approach is to register a time callback function.

Gettimer and timer_create also uses signals to deliver timeout, which will cause trouble in multithreaded programs.

timer_create can specify whether the receiver of the signal is a process or a thread, which is an improvement, but what the signal handler can do is really limited.

timerfd_create turns the time into a file descriptor, and the "file" becomes readable at the moment when the timer times out. In this way, it can be easily integrated into the select/poll framework to handle IO events and timeout events in a unified way, which is also the advantage of Reactor mode.

The signal is converted into a file descriptor for processing

If you want to process the signal, you can also convert the signal into a file descriptor for processing, signalfd

#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);

int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

int timerfd_gettime(int fd, struct itimerspec *curr_value)

Example:

#include <muduo/net/Channel.h>
#include <muduo/net/EventLoop.h>
#include <boost/bind.hpp>
#include <stdio.h>
#include <sys/timerfd.h>

using namespace muduo;
using namespace muduo::net;

EventLoop* g_loop;
int timerfd;

void timeout(Timestamp receiveTime)
{
	printf("Timeout!\n");
	uint64_t howmany;
	::read(timerfd, &howmany, sizeof howmany); // You must read it here, or it will be triggered all the time in the high-level state
	g_loop->quit();
}

int main(void)
{
	EventLoop loop;
	g_loop = &loop;

	timerfd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
	Channel channel(&loop, timerfd);
	channel.setReadCallback(boost::bind(timeout, _1));
	channel.enableReading();

	struct itimerspec howlong;
	bzero(&howlong, sizeof howlong);
	howlong.it_value.tv_sec = 1;
	::timerfd_settime(timerfd, 0, &howlong, NULL);

	loop.loop();

	::close(timerfd);
}

Implementation of muduo timer

muduo Timer is implemented by three classes: TimerId, Timer and TimerQueue. Users can only see the first class, and the other two are internal implementation details.

Timer is a high-level abstraction of timer, and there is no actual timer function

#ifndef MUDUO_NET_TIMER_H
#define MUDUO_NET_TIMER_H

#include <boost/noncopyable.hpp>

#include <muduo/base/Atomic.h>
#include <muduo/base/Timestamp.h>
#include <muduo/net/Callbacks.h>

namespace muduo
{
namespace net
{
///
/// Internal class for timer event.
///
class Timer : boost::noncopyable
{
 public:
  Timer(const TimerCallback& cb, Timestamp when, double interval)
    : callback_(cb),
      expiration_(when),
      interval_(interval),
      repeat_(interval > 0.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_;		// Timer callback function
  Timestamp expiration_;				// Next timeout
  const double interval_;				// Timeout interval. If it is a one-time timer, the value is 0
  const bool repeat_;					// Repeat
  const int64_t sequence_;				// Timer serial number

  static AtomicInt64 s_numCreated_;		// Timer count, the number of timers currently created
};
}
}
#endif  // MUDUO_NET_TIMER_H
#include <muduo/net/Timer.h>

using namespace muduo;
using namespace muduo::net;

AtomicInt64 Timer::s_numCreated_;

void Timer::restart(Timestamp now)
{
  if (repeat_)
  {
    // Recalculate the next timeout
    expiration_ = addTime(now, interval_);
  }
  else
  {
    expiration_ = Timestamp::invalid();
  }
}

Relationship between Muduo classes

The interface of TimerQueue is very simple. There are only two functions: addTimer and cancel. Calling addTimer returns timerld. Add a timer. Timerld is an external class. Call cancel to pass timerld to cancel the timer. In practice, the TimerQueue method is not used, and the function provided by EventLoop is used. TimerQueue calls the actual timer function

EventLoop

runAt: run timer at a certain time

runAfter: run the timer after a period of time

runEvery runs the timer at regular intervals

cancel timer

The selection of TimerQueue data structure can quickly find the expired Timer according to the current time, and also add and delete timers efficiently. Therefore, you can use binary search tree, map or set

typedef std::pair<Timestamp, Timer*> Entry;

typedef std::set<Entry> TimerList;

RVO optimization

#include <iostream>

using namespace std;

struct Foo   
{   
	Foo() { cout << "Foo ctor" << endl; }
	Foo(const Foo&) { cout << "Foo copy ctor" << endl; }
	void operator=(const Foo&) { cout << "Foo operator=" << endl; }
	~Foo() { cout << "Foo dtor" << endl; }
};  

Foo make_foo()   
{   
	Foo f;
	return f; // RVO optimization does not perform copy construction
	//return Foo();  
}
  
int main(void)
{
	make_foo();
	return 0;
}

Keywords: C++ Linux Back-end server

Added by Ixplodestuff8 on Mon, 21 Feb 2022 08:34:16 +0200