xiaoxiaoling

C++博客 首页 新随笔 联系 聚合 管理
  17 Posts :: 2 Stories :: 9 Comments :: 0 Trackbacks

ET和LT:   
   LT一般用在单线程。
   ET和EPOLLONESHOT配合用在多线程共享一个epoll环境下,EPOLLONESHOT标记触发过的事件从epoll中移除,下次必须重新注册,用来防止多线程同时取到同一个socket的事件产生冲突。

epoll_wait 第三个参数 取事件数量:
   单线程模型当然尽可能一次多取一些效率高,多线程为了防止一个线程把所有事件取完其他线程饥饿,ACE实现是只取1个。

错误处理:
   EAGIN | EINTR | EWOULDBLOCK 重试。
   EPOLLERR | EPOLLHUP | EPOLLRDHUP 断开连接。

惊群:
   默认系统都会有这问题,据说新系统有修复不过还是处理一下比较好,一般解决方案是同时只有一个线程等待accept,可以单独线程accept,将连接在分给其他工作线程。nginx是多进程模型,使用了基于共享内存的互斥锁,使得同时只有一个工作进程的epoll含有accept的socket,通过这种方式实现连接数上的负载均衡(连接数少的工作进程得到accept锁的概率高)。

   为了避免大数据量io时,et模式下只处理一个fd,其他fd被饿死的情况发生。linux建议可以在fd联系到的结构(一般都会自己封装一个包含fd和读写缓冲的结构体)中增加ready位,然后epoll_wait触发事件之后仅将其置位为ready模式,然后在下边轮询ready fd列表。


epoll实现:  
   epoll内部用了一个红黑树记录添加的socket,用了一个双向链表接收内核触发的事件。

   注册的事件挂载在红黑树中(红黑树的插入时间效率是logN,其中n为树的高度)。

   挂载的事件会与设备(网卡)驱动建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

   使用mmap映射内存,减少内核态和用户态的不同内存地址空间拷贝开销。每次注册新的事件到epoll中时,会把fd拷贝进内核,通过内核于用户空间mmap同一块内存保证了只会拷贝一次。(返回的时候不需要拷贝,select要)

   执行epoll_ctl时,除了把socket放到红黑树上,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就把socket插入到准备就绪链表里了。链表又是通过mmap映射的空间,所以在传递给用户程序的时候不需要复制(这也是为什么比select效率高的原因,epoll_wait返回的只是就绪队列,不需要轮询 不需要复制完成的事件列表,select,poll实现需要自己不断轮询所有fd集合,直到设备就绪)。

   epoll_wait最后会检查socket,如果是 LT,并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了(LT比ET低效的原因)。

 

可见,如果没有大量的空闲,无效连接,epoll效率不比select高。

测试数据(仅是刚接触go的时候好奇做的参考意义的测试):  

   同样的环境,echo服务器测并发io,单线程epoll qps:45000左右,每连接/协程 go: 50000多,多线程epoll(开6个epoll,每个epoll开8线程,一共48线程):qps 70000多。

 


posted on 2018-07-14 11:17 clcl 阅读(390) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理