之前有用到socket编程中的select函数实现异步,现在要写一个raw socket 来实现本地流量监控,同时记录ICMP,TCP,UDP流量,遇到起线程的问题,所以就有了想法,都是实现异步,select函数和起线程有什么区别,当前流行的服务器解决方案是什么。
答案:
- select函数(poll,epoll类似)本质不是异步,只是实现非阻塞的方法。
- 当前流行的解决方案是:event loop + thread pool,就是事件轮循加线程池的做法。
select 函数本质不是异步
我在之前写的文章socket编程的几个问题中提到过select函数,它可以监控接收或发送事件的情况,若需要接收或发送,就进行处理,如果出现错误或者没有需要处理的事件就跳出执行之后的事件,不必一直等待。而传统的阻塞模式会一直等待事件发生才返回。
这是实现非阻塞的方法,但是本质上它是一个同步的函数,是同步执行的。换句话说,它实现的功能本身和异步没有关系,但是可以异步地执行select函数。
参考1
参考2
事件轮循加线程池
首先,了解一下线程池的概念。线程池,字面理解,就是一个放了很多线程的地方,我们把它叫做线程池,我们需要了解的是为什么需要用到它。
假设一个服务器完成一项任务所需时间为:T1 创建线程时间, T2 在线程中执行 任务的时间,T3 销毁线程时间。
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
也就是说,采用线程池是为了免去创建线程和销毁线程的时间,我们循环利用线程池的线程,而不需要每一个连接对应一个线程,这样会大大减少创建和销毁线程的个数。举个栗子:
假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。
那么具体事件轮循加线程池的怎么做。
先用select接口(poll/epoll,kq,iocp)接受请求,这样可以保证并发,在这个环节他只管收,不处理业务,把FD放到一个buffer(一个q里面),然后业务处理模型对接线程池。可以使复杂业务处理上的负担被分担。select+线程池,这样兼顾了并发(牺牲了一点性能),又保证了因为逻辑代码的简洁性。
也就是说,我select函数负责接收判断请求状态(即判断有没有可读或者可写的套接字,有的话加入到数组中),而内部的处理接收或发送信息的过程交给线程池来做。
总结
多线程,异步这些概念总是含含糊糊,真真假假地在脑海里闪现,一会觉得理解了,暗自庆幸,一会又想不通,纠结万分。有时候不必太过钻牛角尖,这些概念不过人取的名字,其实并不像看上去那样分得那么开或是靠得那么紧。可能是某一个上古程序员想实现一边看电影一边听音乐,实现的这个目的呢,起个名字,叫异步吧,怎么解决呢,想了个办法,用多线程吧,囔,起了个名字,多线程。