源码下载以及安装点击链接https://blog.csdn.net/YoungSusie/article/details/90021742
分类 Muduo网络库编程 学习笔记
Event Loop 有一个非常有用的功能:在它的IO线程内执行某个用户任务的回调, 即EventLoop::runInLoop(const Functor & cb), 其中 functor是 boost::function
有了这个功能,我们可以轻易地在线程间进行任务调配,这样可以在不用加锁的情况下保证线程的安全性。
由于IO线程平时阻塞在事件循环EventLoop 的 poll函数中 ,为了让其他线程能立刻执行用户回调,需要立刻唤醒它。传统的方法是使用pipe(),IO 线程始终监视此管道的readable 事件,在需要唤醒的时候,其他线程往管道里写一个字节,这样IO线程就从IO multiplexing 阻塞调用中返回。现在有了Linux的eventfd ,可以高效地唤醒,因为其不必管理缓冲区。
概念
是一个用来通知事件的文件描述符,就像前一节所述的timerfd 是定时器的文件描述符,二者都是内核向用户空间的应用发送通知的机制,可以有效地被用来实现用户空间的事件/通知驱动的应用程序。
使用
使用eventfd() 系统调用,需要包含头文件 #include
与pipe(用于事件通知的时候) 的区别主要在于两点:首先,pipe 需要两个文件描述符,fd[0] 为读而打开,fd[1] 为写而打开;eventfd 只需要一个文件描述符。其次pipe 需要管理不定长的缓冲区,eventfd缓冲区为定长8字节。
int eventfd(unsigned int initval, int flags);
创建一个eventfd 对象,该对象是一个内核维护的无符号的64位整型计数器。
Linux Programmer’s Manual - EVENTFD
void EventLoop::runInLoop(const Functor & cb)
{
if (isInLoopThread()) //如果在当前IO 线程,直接回调
{
std::cout << "In Thread" << std::endl;
cb();
}
else{
std::cout << "Not in Thread " << std::endl;
queueInLoop(cb);//如果不在当前线程,将回调加入队列
}
}
void EventLoop::queueInLoop(const Functor & cb)
{
{
MutexLockGuard lock(mutex_);
pendingFunctors_.push_back(cb);
}
if (!isInLoopThread() || callingPendingFunctors_)
{ //如果不在IO线程或者此时正在pending functor ,则wakeup
wakeup();
}
}
思考:
为什么只有在IO线程的事件回调中调用queueInLoop () 才无须wakeup()?
换句话说:为什么在pending functor 的时候仍然需要wakeup?
因为由于doPendingFunctor() 中调用的Functor()可能再调用queueInLoop(),这是如果不 wakeup,可能或导致新加入的cb 回调不及时。
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;
}
void EventLoop::wakeup()
{
uint64_t one = 1;
ssize_t n = ::write(wakeupFd_,&one,sizeof one);
if( n != sizeof one)
{
std::cout << "EventLoop::wakeup error";
_exit(-1);
}
}
前面TimerQueue class 对外开放的接口 addTimer() 只能在IO线程调用,因此 EventLoop::runAfter() 系列的函数不是线程安全的。因为 EventLoop::runAfter() 会调用addTimer() ,如果是在其他的线程调用EventLoop::runAfter() ,也就是在其他的线程调用addTimer(),则不是安全的。
新增一个TimerQueue class 的private 成员函数 addTimerInLoop,在addTimer 中调用这个函数,将实际的工作转移到IO线程中。
将addertime拆分为两部分,拆分后的addtimer只负责转发,addTimerInLoop负责完成修改定时器列表的工作。线程安全的。
TimerId TimerQueue::addTimer(const TimerCallback & cb,Timestamp when,double interval)
{
Timer* timer = new Timer(cb,when,interval);
loop_->runInLoop(boost::bind(&TimerQueue::addTimerInLoop,this,timer));
return TimerId(timer);
}
void TimerQueue::addTimerInLoop(Timer* timer)
{
loop_->assertInLoopThread();
bool earliestChanged = insert(timer); ///已经失效的定时器
if(earliestChanged)
{ resetTimerfd(timerfd_,timer->expiration());}
}
IO线程不一定是主线程, 我们可以在任何一个线程创建并运行eventloop, 一个程序也不止一个IO线程,可以按照优先级不同将不同的socket 分给不同的IO线程,避免优先级反转。
EventLoopThread class会启动自己的线程,并在自己的线程中启动loop()。这个class中关键的是startLoop() , 这个函数会返回新线程中EventLoop对象的地址,因此用条件变量来等待线程的创建与运行。
如果需要等待某个条件成立,应该使用条件变量,这个变量是一个或者多个线程等待某个bool 表达式为真,即等待别的线程唤醒它。条件变量的正确使用方法:
对于wait 端,对于等待条件端:
伪代码可以表示为
MutexLock mutex;
Condition cond;
std::deque<int> queue
int dequeue()
{
MutexLockGuard lock(mutex);
while(queue.empty())
{
cond.wait();
}
int top = queue.front();
queue.pop_front();
return top;
}
必须用while 来等待 条件变量,不能用if,否则会引起虚假唤醒。
在多核处理器中,pthread_cond_signal 可能会同时激活多余一个线程(阻塞在条件变量上的线程)。结果是,当一个线程调用pthread_cond_signal 后,多个调用pthread_cond_wait 的线程返回,这种效应称为虚假唤醒。
虚假唤醒 解决 方法是将使用while,而不是if。
如果用if 作为判断的条件,当一个线程调用pthread_cond_signal后,阻塞的线程被唤醒,这个时候应该还是先检查下条件是否满足,再决定是否往下运行,如果不判断而往下运行导致错误。
对于signal 端,对于发送条件端:
伪代码可以表示为
MutexLock mutex;
Condition cond;
std::deque<int> queue
void enqueue(int x)
{
MutexLockGuard lock(mutex);
queue.push_back(x);
cond.notify(); //signal的包装
}
条件变量是非常底层的同步原语,很少直接使用,一般都是用来实现高层的同步措施。
分类 Muduo网络库编程 学习笔记