说下自己的理解,供参考。假设题主了解网络编程和计算机系统的一些基本概念。
简单概括来说,事件驱动是实现并发处理的一种方式。
我们就以HTTP请求的处理过程为例,为简化说明,仅考虑网络IO,不考虑文件IO和数据库等其他过程,也不考虑多核系统。
考虑采用如下最简模型来处理HTTP请求:
main_loop:
accept()
recv()
parse()
send()
close()
来一个连接,读取数据(请求),解析请求内容,返回数据(应答)。
同一时间只为一个客户端服务。在为A客户端服务的过程中,B客户端必须等待。
这种方式非常简单直接,容易理解,但其无法满足现实场景的需要——不支持并发。
现实中,客户端的请求是并发的:即当一个客户端的请求还在处理时,另外一个客户端的请求就会达到,甚至多个客户端的请求同时达到。
而且,recv 和 send等涉及网络操作的API由于网络数据发送与到达的不确定性,可能需要等待,CPU会空闲下来——但这种模型下即使CPU空闲了也无法处理其他客户端的请求,浪费了CPU。
我们采用如下多线程模型,可以解决上述问题:
main_loop:
accept()
start_thread(thread_loop)
thread_loop:
recv()
parse()
send()
close()
exit thread()
即每个客户端在一个独立的线程中处理。
当一个客户端的线程执行网络操作需要等待时,会被操作系统调度出去,执行其他需要干活儿的线程。
似乎完美了解决了我们的问题?
然而并没有。
因为操作系统创建线程的开销是比较大的,能够支持的线程数量是有限的,通常是几万的级别,如果线程太多,就会有很多的CPU浪费在了线程的创建、销毁、调度等管理操作上。
所以为了充分发挥CPU的能力,支持更多的并发数量,,在Linux上有另外一种处理并发的方式:
内核提供了监听大量网络连接(句柄)可读、可写等事件的机制和接口。
应用把需要监听对象以及关心的事件注册给内核,内核在有事件达到时通知应用处理。
基于这种机制处理并发就是事件驱动。
事件驱动机制的基本模型是:
create_listen_socket()
register_event_for_listen_socket()
main_loop:
wait_for_event()
check_events:
if listen_socket has event(new client coming) :
accept()
register_event_for_client_socket()
if client_socket has event(new data coming):
recv()
parse()
send()
但这里有一个问题,有可能一个客户端刚读取了一部分数据,就没了,剩下的还在网络中没过来,需要继续等待。
这就需要把当前的读取内容和请求处理状态(也即上下文)保存起来,继续处理其他客户端的事件。
然后下次这个客户端再有事件到来时再找回上下文继续处理。
这其实需要应用自己做一些任务调度相关的上下文保存和切换工作。
当使用多线程处理并发时,操作系统帮我们做了这些工作,我们无需关心任务切换。
因为一个线程就只处理一个客户端,反复调用recv把一个请求的数据读完然后解析处理就可以了,也不用担心没数据到来时,recv阻塞了其他客户端的处理。
所以多线程编写并发代码非常简单直接。
如上,事件驱动机制是Linux上解决并发问题的一种高效编程模型。
应用反复探测事件,对接收到的事件进行逐个处理的过程就是事件循环。
那么同步和异步概念体现在哪里呢?
所谓同步就是我们执行一个任务,一直等待任务执行结束。
所谓异步就是我们执行一个任务,不等待任务执行结束,继续去干其他活儿,任务结果后有个通知,或者干脆不关心任务的执行结果。
在多线程模型中,每接收到一个新的客户端就创建一个线程处理,这就是一种异步处理。
在事件驱动模型中,当没有数据可读时,就把这个客户端继续放到监听队列中监听,也是一种异步。
如果我们考虑文件IO,把IO请求丢给另外一个或一组线程(线程池)处理,处理完后通知主线程,也是一种异步。