线程唤醒方法
一个线程如何通知另一个等待中的线程,可以有三个方法:
1、pipe 管道有一对文件描述符。pipe[0] 对应管道的读端,pipe[1] 对应管道的写端,等待的线程关注pipe[0]的读端,通知线程向pipe[1] 写入数据,pipe[0] 就变的可读了,等待线程获得通知就唤醒线程。
2、socketpair 和管道一样有一对文件描述符,不同的是可以双向通信。
3、eventfd。等待线程监听文件描述符可读事件,通知线程只要往这个线程写入数据,等待线程就会获得通知。是一个比 pipe 更高效的线程间事件通知机制,一方面它比 pipe 少用一个 file descripor,节省了资源;另一方面,eventfd 的缓冲区管理也简单得多,全部“buffer” 只有定长8 bytes,不像 pipe 那样可能有不定长的真正 buffer。
以上方法可以用在进程和线程,线程还可以用条件变量。不同的是上面三种都可以统一纳入IO复用来管理,而条件变量不行。
int createEventfd()
{
int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (evtfd < 0)
{
LOG_SYSERR << "Failed in eventfd";
abort();
}
return evtfd;
}
EventLoop::EventLoop()
: looping_(false),
quit_(false),
eventHandling_(false),
callingPendingFunctors_(false),
threadId_(CurrentThread::tid()),
poller_(Poller::newDefaultPoller(this)),
timerQueue_(new TimerQueue(this)),
wakeupFd_(createEventfd()), // 创建eventfd
wakeupChannel_(new Channel(this, wakeupFd_)), // eventfd 的channel
currentActiveChannel_(NULL)
{
LOG_TRACE << "EventLoop created " << this << " in thread " << threadId_;
// 如果当前线程已经创建了EventLoop对象,终止(LOG_FATAL)
if (t_loopInThisThread)
{
LOG_FATAL << "Another EventLoop " << t_loopInThisThread
<< " exists in this thread " << threadId_;
}
else
{
t_loopInThisThread = this;
}
wakeupChannel_->setReadCallback(
boost::bind(&EventLoop::handleRead, this));
// we are always reading the wakeupfd
wakeupChannel_->enableReading();
}
// 唤醒
void EventLoop::wakeup()
{
uint64_t one = 1;
//ssize_t n = sockets::write(wakeupFd_, &one, sizeof one);
ssize_t n = ::write(wakeupFd_, &one, sizeof one);
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
}
}
// eventfd 事件处理函数
void EventLoop::handleRead()
{
uint64_t one = 1;
//ssize_t n = sockets::read(wakeupFd_, &one, sizeof one);
ssize_t n = ::read(wakeupFd_, &one, sizeof one);
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8";
}
}
EventLoop中非常有用的一个函数runInLoop
在IO线程中执行某个回调函数,改函数可以跨线程调用
// 在I/O线程中执行某个回调函数,该函数可以跨线程调用
void EventLoop::runInLoop(const Functor& cb)
{
if (isInLoopThread())
{
// 如果是当前IO线程调用runInLoop,则同步调用cb
cb();
}
else
{
// 如果是其它线程调用runInLoop,则异步地将cb添加到队列
// 以便让EventLoop 所在的线程执行这个回调函数
queueInLoop(cb);
}
}
void EventLoop::queueInLoop(const Functor& cb)
{
{
MutexLockGuard lock(mutex_);
pendingFunctors_.push_back(cb);
}
// 调用queueInLoop的线程不是当前IO线程需要唤醒
// 或者调用queueInLoop的线程是当前IO线程,并且此时正在调用pending functor,需要唤醒
// 只有当前IO线程的事件回调中调用queueInLoop才不需要唤醒
if (!isInLoopThread() || callingPendingFunctors_)
{
wakeup();
}
}
while (!quit_)
{
activeChannels_.clear();
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
if (Logger::logLevel() <= Logger::TRACE)
{
printActiveChannels();
}
eventHandling_ = true;
for (ChannelList::iterator it = activeChannels_.begin();
it != activeChannels_.end(); ++it)
{
currentActiveChannel_ = *it;
currentActiveChannel_->handleEvent(pollReturnTime_);
}
currentActiveChannel_ = NULL;
eventHandling_ = false;
// 让IO线程也能执行一些计算任务,IO不忙的时候,处于阻塞状态
doPendingFunctors(); // 其他线程或者本线程添加的一些回调任务
}
void EventLoop::doPendingFunctors()
{
std::vector<Functor> functors;
callingPendingFunctors_ = true;
{
MutexLockGuard lock(mutex_);
functors.swap(pendingFunctors_);
}
for (size_t i = 0; i < functors.size(); ++i)
{
functors[i]();
}
callingPendingFunctors_ = false;
}
不是简单地在临界区内依次调用Functor,而是把回调列表swap到functors中,这样一方面减小了临界区的长度(意味着不会阻塞其它线程的queueInLoop()),另一方面,也避免了死锁(因为Functor可能再次调用queueInLoop())。
由于doPendingFunctors()调用的Functor可能再次调用queueInLoop(cb),这时,queueInLoop()就必须wakeup(),否则新增的cb可能就不能及时调用了。
muduo没有反复执行doPendingFunctors()直到pendingFunctors为空,这是有意的,否则IO线程可能陷入死循环,无法处理IO事件。