使用eventfd唤醒阻塞在select、poll、epoll的IO复用

还是以muduo为例:
EventLoop中有两个成员变量与唤醒阻塞的IO复用有关,wakeupFd_wakeupChannel_;

class EventLoop : noncopyable
{
public:
	...
	void wakeup(); // 唤醒阻塞的IO复用函数
	...

private:
	...
	void handleRead();  // wakeupFd_可读时的回调函数

private:
	int wakeupFd_;
	std::unique_ptr<Channel> wakeupChannel_;
};

wakeupChannel_是对wakeupFd_的包装,同时注册了wakeupFd_的可读事件,可读事件的回调函数是EventLoop::handleRead()

EventLoop::EventLoop()
  : looping_(false),
    quit_(false),
    eventHandling_(false),
    callingPendingFunctors_(false),
    iteration_(0),
    threadId_(CurrentThread::tid()),
    poller_(Poller::newDefaultPoller(this)),
    timerQueue_(new TimerQueue(this)),
    wakeupFd_(createEventfd()), // 这是重点
    wakeupChannel_(new Channel(this, wakeupFd_)),
    currentActiveChannel_(NULL)
{
	...	
	wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this)); // 这是重点
  	// we are always reading the wakeupfd
  	wakeupChannel_->enableReading(); // 这是重点
}

当需要唤醒阻塞在IO复用的主线程时,只需要调用EventLoop::wakeup()

void EventLoop::wakeup()
{
	uint64_t one = 1;
	ssize_t n = sockets::write(wakeupFd_, &one, sizeof one);
	if (n != sizeof one)
	{
    	LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
    }
}
ssize_t sockets::write(int sockfd, const void *buf, size_t count)
{
	return ::write(sockfd, buf, count);
}

它会调用write()wakeupFd_中写入数据,wakeupFd_变成可读,于是阻塞的IO复用就此返回。
接着EventLoop::handleRead()被调用。因为muduo中无论是poll还是epoll都被设置为水平触发(level triggered),
所以wakeupFd_可读后必须将其中的数据读出,否则它会一直触发可读。处理方式也很简单,直接使用read()将其中数据读出即可。

void EventLoop::handleRead()
{
	uint64_t one = 1;
	ssize_t n = sockets::read(wakeupFd_, &one, sizeof one);
	if (n != sizeof one)
	{
		LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8";
	}
}

需要注意的是,无论是对wakeupFd_写入还是读出,其操作的对象类型都必须是大于等于8字节的数据类型,可以是int64或者uint64
详细信息可参考:linux进程(线程间)间通信-eventfd

你可能感兴趣的:(linux,muduo)