还是以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