ACE_Reactor二ACE_Dev_Poll_Reactor

ACE_Reactor一些重要的细节
看下具体ACE_Dev_Poll_Reactor的实现,如何将一个处理集和handle关联起来,代码如下:

int ACE_Dev_Poll_Reactor ::register_handler_i(handle,event_handler,mask)
//step 1
if(this->handler_rep_.find(handle)==0)
{
    //Handler not present in the repository.Bind it.
    if(this->handler_rep_.bind(handle,mask)!=0)
        return -1;
}

//step2
//All but the notify handler get registered with oneshot to facilitate auto suspend before the upcall.See dispatch_io_event for more information
if(event_handler!= this->notify_handler_)
    epev.events |= EPOLLONESHOT;
if(::epoll_ctl(this->poll_fd_,op,handle,&epev) == -1)
    ......

step1
首先就是要将handle和处理这个handle的event_handler绑定,若已有处理的event_handler,则只单纯将可能新增的mask添加进监测的epoll函数参数中。
这里的handler_rep_是一个Handler_Repository类的指针,其内部是一个map表可以对handle和event_handler进行有效关联对应和存储。
step2
从上面可以看出,如果是epoll的话,且注册了notify handler, 则epoll_wait会自动将EPOLLONESHOT标识添加上,这样在事件被监测到得分发处理时就不用再手动的去suspend。

Notify机制的好处是:
1. 让反应器拥有了处理无限处理器的能力
2. 其次是提供了必要时解除反应器事件检查阻塞的能力。

Reactor的notify()让用户直接提供给Reactor待通知反应器的指针,而这些处理器无需注册到反应器上,从而提供了无限的扩展能力。
不过在ACE中显然Notify机制有不同的实现方式。一种是采用ACE_Pipe的实现,这个属于默认的方式。另一种则是内存队列保存Notify消息。你可以通过定义ACE_HAS_REACTOR_NOTIFICATION_QUEUE的宏编译ACE,这样ACE将不使用ACE_Pipe作为Notify消息的管道,而使用一个自己的内存队列保存Notify消息。
但是需要注意的是,在使用ACE_Pipe的实现中,如果使用不当,可能会造成严重的后果。
如果你用不到Notify机制,最好在ACE_Reactor初始化的时候彻底关闭Notify机制。很多Reactor的初始化函数都提供了关闭notify pipe的方式。比如ACE_Select_Reactor_T的open函数的disable_notify_pipe参数。当其为1的时候表示关闭notify 管道。

潜在的风险:
1. 处理器被销毁后,排队等候的对应通知才被分派
2. ACE_Pipe的数据缓冲是有限的,大量通知到来可能会造成阻塞甚至死锁

因为通知的信息(包括处理器指针和掩码)以流数据的方式被写入ACE_Pipe,不能进行遍历查找,所以当对应处理器被销毁后,如果其在ACE_Pipe的数据区中还有存储有通知,则ACE_Reactor将会在分派中使用悬空指针,造成未知的后果。另外,在ACE_Pipe的数据缓冲已满情况下,在处理器的回调中依然发送通知,就会因阻塞发生死锁。

通过

int ACE_Dev_Poll_Reactor ::notify(eh,mask,timeout)
{
    //pass over both the event_handler* and * the mask o allow the caller to dictate which Event_Handler method the receiver invokes.Note that this call can timeout.
    n = this->notify_handle_->notify(eh,mask,timeout);
    return n=-1?-1:0;
}

在源码的owner接口中,看到了这样一句注释
//There is no need to set the owner of the event loop.Multiple threads may invoke the event loop simulataneously.
说明有可能多个线程同时调用ACE_Dev_Poll_Reactor的event_loop函数,网上很多示例代码写的都比较简单,例如:

65 int main(void)
     66 {
     67         ACE::init();
     68 
     69         ACE_Dev_Poll_Reactor dev_reactor(1024);
     70         ACE_Reactor reactor(&dev_reactor);
     71         ACE_Reactor::instance(&reactor);
     72 
     73         TestAccptor accptor;
     74         ACE_INET_Addr addr(10000);
     75         if( accptor.open(addr) == -1 ) {
     76                 cout << "Open port failed .. " << endl;
     77                 return -1;
     78         } 
     79        
     80        cout << "Open port ok .. " << endl;
     81 
     82 
     83         ACE_Reactor::instance()->run_reactor_event_loop();
     84         ACE::fini();
     85         return 0;
     86 }

这里有一个问题,监测事件触发和同时执行的是不是就只有一个main函数的这一个主线程就完全搞定了。现在看是这样的,但是实际中应该是不同线程都可以去调用上述的run_reactor_event_loop()来完成事件的监测和触发才对。

上述的Notify的实现机制,在其open函数中可能可以看出点东西来:

int ACE_Dev_Poll_Reactor_Notify ::open(ACE_Reactor_Impl*r,ACE_Timer_Queue*in disable_notify_pipe)
{
    if(0==disable_notify_pipe)
    {
        this->dp_reactor=(ACE_Dev_Poll_Reactor*)r;
        if(this->notification_pipe_.read_handle()==-1)
            return -1;

        #if defined (F_SETFD)
        ACE_OS::fcntl(this->notification_pipe_.read_handle(),F_SETFD,1);
        ACE_OS::fcntl(this->notification_pipe_.write_handle(),1);       


        #if defined (ACE_HAS_REACTOR_NOTIFICATION_QUEUE)
        if(this->notification_queue_.open()==-1)
            return -1;
        if(ACE_OS::set_flags(this->notification_pipe_.write_handle(),ACE_NONBLOCK) == -1)
            return -1;  
        #endif
        if(ACE_OS::set_flags(this->notification_pipe_.read_handle(),ACE_NONBLOCK) == -1)
            return -1;          

    }
    return 0;
}

若是不用pipe机制作为Notify通知机制实现,则这个open直接就return掉了。

再来看:

int ACE_Dev_Poll_Reactor ::dispatch_io_event(Token_Guard &guard)
{
    {
        CGuard(this->repo_loc_)//对于handler的respository访问,因此加锁保护
        //step1 获取event_handler
        Event_Tuple *info=this->handle_rep_.find(handle);
        if(info==0) return 0;//说明已经没有对应的event_handler了
        if(info->suspended) return 0;//说明其他线程可能已经更改了这个handle的mask。
        short revents=(pollfd*)pfds->revents;//即已触发的事件
        //step2 根据revents来指定
         disp_mask=WIRTE/READ/EXCPET_MASK;
         callback=handle_output/input/exception;
        //step3 将当前的event_handler标识置为suspended为true,以防止被重复触发
        if(eh!=this->notify_handler_)
            info->suspended=true;
    }
    //step4 将notify的event_handler直接处理,分发,且注意不要suspend和resume notify的handler,因为如果要这样做可能引起无休止的暂停和恢复,又要去经常等待获取token。
    if(eh==this->notify_handler_)//说明是notify handler,直接处理
    {
        ACE_Notification_Buffer b;
        notify_handler_->dequeue_one(b);
        guard.release_token();
        return notify_handler_->dispatch_notify(b);//直接去分发处理notify消息
    }

    //step5 执行handler中对应的回调
    {
        ACE_Dev_Poll_Handler_Guard eh_guard(eh);
        guard.release_token();
        status=this->upcall(eh,callback,handle);

    }
    //step6 检查回调的返回值status,是否等于0去恢复由于执行回调而暂停的handle
    //step7 检查handle对应的回调的返回值,若小于0则需要remove_handler_i
}

而上述upcall在实际的内联实现文件中:

int ACE_Dev_Poll_Reactor ::upcall(eventhandler,handle)
{
    do 
    {
        status=(event_handler->*callback)(handle);
    }
    while(status>0 && event_handler != this->notify_handler_);
    return status;
}

上面虽然对于注册handler和handle关联以及最终事件触发后如何调用handler中的回调函数理清了。但是有一点,如何完成多路复用和事件分离,并没有很清楚。这里就要提到

int ACE_Dev_Poll_Reactor::work_pending_i    (   ACE_Time_Value *    max_wait_time    )  [protected]

01071 {
01072   ACE_TRACE ("ACE_Dev_Poll_Reactor::work_pending_i");
01073 
01074   if (this->deactivated_)
01075     return 0;
01076 
01077 #if defined (ACE_HAS_EVENT_POLL)
01078   if (this->start_pevents_ != this->end_pevents_)
01079 #else
01080   if (this->start_pfds_ != this->end_pfds_)
01081 #endif /* ACE_HAS_EVENT_POLL */
01082     return 1;  // We still have work_pending(). Do not poll for
01083                // additional events.
01084 
01085   ACE_Time_Value timer_buf (0);
01086   ACE_Time_Value *this_timeout = 0;
01087 
01088   this_timeout = this->timer_queue_->calculate_timeout (max_wait_time,01089                                                         &timer_buf);
01090 
01091   // Check if we have timers to fire.
01092   const int timers_pending =
01093     ((this_timeout != 0 && max_wait_time == 0)
01094      || (this_timeout != 0 && max_wait_time != 0
01095          && *this_timeout != *max_wait_time) ? 1 : 0);
01096 
01097   const long timeout =
01098     (this_timeout == 0
01099      ? -1 /* Infinity */
01100      : static_cast<long> (this_timeout->msec ()));
01101 
01102 #if defined (ACE_HAS_EVENT_POLL)
01103 
01104    // Wait for events.
01105    const int nfds = ::epoll_wait (this->poll_fd_,01106                                   this->events_,01107                                   this->size_,01108                                   static_cast<int> (timeout));
01109 
01110   if (nfds > 0)
01111     {
01112       this->start_pevents_ = this->events_;
01113       this->end_pevents_ = this->start_pevents_ + nfds;
01114     }
01115 
01116 #else
01117 
01118   struct dvpoll dvp;
01119 
01120   dvp.dp_fds = this->dp_fds_;
01121   dvp.dp_nfds = this->size_;
01122   dvp.dp_timeout = timeout;  // Milliseconds
01123 
01124   // Poll for events
01125   const int nfds = ACE_OS::ioctl (this->poll_fd_,DP_POLL,&dvp);
01126 
01127   // Retrieve the results from the pollfd array.
01128   this->start_pfds_ = dvp.dp_fds;
01129 
01130   // If nfds == 0 then end_pfds_ == start_pfds_ meaning that there is
01131   // no work pending. If nfds > 0 then there is work pending.
01132   // Otherwise an error occurred.
01133   if (nfds > -1)
01134     this->end_pfds_ = this->start_pfds_ + nfds;
01135 #endif /* ACE_HAS_EVENT_POLL */
01136 
01137   // If timers are pending,override any timeout from the poll.
01138   return (nfds == 0 && timers_pending != 0 ? 1 : nfds);
01139 }

上述代码是5.5.2版本而在最新的6.3.3版本中这里的epoll_wait 第三个参数即MaxEvents是直接用魔数1来指定的。也即每次最多返回1个触发的事件。
其他的线程当然也可以调用handle_events来监测和处理事件,但是在handle_events中对这些其他的线程也做了限制,即必须是token的owner才能执行,否则直接return。
而如何成为owner,则需要调用函数Token_Guard 的函数acquire_quietly,其代码如下:

01144 {
01145   ACE_TRACE ("ACE_Dev_Poll_Reactor::handle_events");
01146 
01147   // Stash the current time
01148   //
01149   // The destructor of this object will automatically compute how much
01150   // time elapsed since this method was called.
01151   ACE_MT (ACE_Countdown_Time countdown (max_wait_time));
01152 
01153   Token_Guard guard (this->token_);
01154   int result = guard.acquire_quietly (max_wait_time);
01155 
01156   // If the guard is NOT the owner just return the retval
01157   if (!guard.is_owner ())
01158     return result;
01159 
01160   if (this->deactivated_)
01161     return -1;
01162 
01163   // Update the countdown to reflect time waiting for the mutex.
01164   ACE_MT (countdown.update ());
01165 
01166   return this->handle_events_i (max_wait_time,guard);
01167 }

这样就变成了串行化的使用epoll获取poll去监测事件,然后监测到线程去处理,而没有成为token的owner的线程则持续等待。
参考:

http://www.jb51.cc/article/p-dqycvzeu-hz.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom