当前位置: 首页 > Linux

高性能服务器开发基础系列(一)主线程和工作线程的分工

时间:2023-04-06 19:17:34 Linux

为了顺利处理多个客户端连接,服务器一般在某个线程A接受新的客户端连接,并为新连接fd生成套接字,然后将这些新连接的socketfd交给另外几个工作线程B1、B2、B3、B4。这些工作线程处理这些新连接上的网络IO事件(即发送和接收数据),同时处理系统。其他一些业务。这里我们称线程A为主线程,B1、B2、B3、B4等称为工作线程。工作线程的代码框架一般如下:while(!m_bQuit){epoll_or_select_func();handle_io_events();handle_other_things();}在epoll_or_select_func()中,使用select()或poll/epoll()检测socketfd上的io事件,如果存在这些事件,则下一步是handle_io_events()处理这些事件(发送和接收数据),完成后可能还需要做一些系统的其他工作,即调用handle_other_things()。这样有三个好处:线程A只需要处理新连接的到来,不需要处理网络IO事件。由于网络IO事件的处理一般比较慢,如果新连接和网络IO都在线程A中处理,线程可能会因为线程忙于处理IO事件而不能及时处理来自客户端的新连接,这就是很坏。线程A接收到的新连接,可以根据一定的负载均衡原则,将新的socketfd分配给工作线程。常用的算法,比如roundrobin,都是轮询机制,即假设不考虑连接断开,一个新的连接过来,分配给B1,一个分配给B2,另一个分配给B3,然后另一个分配给B4。如此反复,也就是说,线程A记录了每个worker线程上socketfds的个数,从而最大限度地平衡资源,避免出现有的worker线程“忙死”,有的“闲死”的现象”。即使工作线程没有满载,也可以让工作线程去做其他事情。例如,现在有四个工作线程,但只有三个连接。然后线程B4可以在handle_other_thing()中做一些其他的事情。下面讨论一个很重要的效率问题:在上面的while循环中,epoll_or_selec_func()中的epoll_wait/poll/select等函数一般都会设置一个超时时间。如果超时设置为0,那么在没有任何网络IO时间和其他任务处理的情况下,这些工作线程实际上会空闲,白白浪费cpu时间片。如果设置的超时时间大于0,在没有网络IO时间的情况下,epoll_wait/poll/select仍然要暂停指定的时间才返回,导致handle_other_thing()不能及时执行,影响其他不能执行的任务及时处理,也就是说,其他任务一旦产生,其处理有一定的延迟。这也不好。那么如何解决这个问题呢?其实我们想要达到的效果是,如果没有网络IO时间和其他任务需要处理,这些工作线程最好挂起而不是空转;如果还有其他任务需要处理,这些工作线程应该能够立即处理这些任务,而不是在epoll_wait/poll/selec挂起指定时间后才开始处理这些任务。我们采取以下方法来解决这个问题。以linux为例,不管epoll_fd上有没有文件描述符fd,我们都绑定一个默认的fd,这个fd叫做wake-upfd。当我们需要处理其他任务时,只需要向这个唤醒的fd写入1个字节,让这个fd立即变为可读,epoll_wait()/poll()/select()函数立即被唤醒,并返回,和handle_other_thing()可以立即执行,处理其他任务。相反,当没有其他任务和网络IO事件时,epoll_or_select_func()只是挂在那里什么都不做。这个唤醒fd在linux平台下可以通过以下方式实现:pipeline管道,创建一个pipeline,将pipeline绑定到epoll_fd上。需要时,将一个字节写入管道的一端,工作线程立即被唤醒。linux2.6中的新eventfd:inteventfd(unsignedintinitval,intflags);步骤一样,将生成的eventfd绑定到epoll_fd。需要时,向这个eventfd写入一个字节,工作线程立即被唤醒。第三种方法最方便。即linux特有的socketpair。一个socketpair是一对互相连接的socket,相当于server和client的两个端点,每一端都可以读写数据。intsocketpair(intdomain,inttype,intprotocol,intsv[2]);调用该函数返回的两个套接字句柄为sv[0]和sv[1],其中任意一个为writebytes,另一个为Receivebytes。将接收到的字节套接字绑定到epoll_fd。需要时,向另一个写套接字写入一个字节,工作线程立即被唤醒。如果使用socketpair,则域参数必须设置为AFX_UNIX。因为在windows中,select函数只支持检测socketfd,所以方法3的原理只能在windows上使用。而你需要手动创建两个socket,然后一个连接另一个,将readsection绑定到select的fd上。这是跨两个平台编写代码时需要注意的地方。欢迎关注公众号『easyserverdev』。如果您有任何技术或专业问题需要我帮助,您可以通过这个公众号与我取得联系。本公众号不仅分享高性能服务器开发经验和故事,还免费为技术朋友们提供技术解答和解答。有什么问题可以直接在微信上留言公众号,我会第一时间回复你。