随着我们的前进,让我们来研究一下异步编程的真正非阻塞性,以及作为事件的IO中断是如何被用来制作能够承受巨大工作负荷的系统的。
什么是基于事件的编程?
通过基于事件的风格,我们确保我们的应用程序不包含阻塞的代码。在启动一个像数据库调用这样的IO操作时,一个线程会切换到其他任务。然后,当IO操作完成时,这个线程会被通知/中断,以便它能处理结果。从调用线程的角度来看,整个IO阶段是外包给其他人的,当工作完成后,他们会发送一个通知。
正如你所看到的,这与我们在基于线程的异步编程讨论中所讨论的内容非常相似。线程池和这个模型的一个关键区别是,基于线程池的模型假装是无阻塞的,但事实上,它的块只是被推送到其他线程,而这个模型根本没有阻塞的代码。在调用线程的另一边没有必要有一个线程池来接收和通知调用者。但问题是,在IO操作完成后,谁来发布中断。
基于事件的编程如何工作?
UNIX及其衍生产品支持接受来自应用程序的IO钩,跟踪IO生命周期,并在IO完成后通知应用程序。通过使用这一功能,应用程序可以专注于触发IO和处理其结果,而不必担心管理IO。libuv是一个最初用C语言编写的用于Node.js的C语言库,抽象出了这种相同的IO管理。
nix*系统中的epoll
I/O是通过打开一个套接字并发出一个系统调用来开始在该套接字上发送和接收数据。当请求的数据被发送后,使用每请求一次的单线程模型的应用程序将简单地轮询套接字,以检查它何时收到结果。
操作系统中内置有一个轮询程序,可以非常有效地处理套接字的轮询。我们的应用程序将自己的套接字注册为轮询套接字,并给它一个钩子,即一个回调,一旦入站数据流准备好进行处理,当套接字收到响应时,操作系统就会用这个钩子通知应用程序。现在应用线程是自由的,它可以被用来处理非IO任务,因为没有阻塞的IO,允许它实现大规模的规模。在libuv库中,epoll是负责对这些IO套接字进行轮询的。
所有在epoll注册的套接字都会被持续轮询,通常是通过一个线程。有了响应数据和线程执行环境(应用线程在移交给epoll时也存储在这里),epoll会调用应用线程给它的回调。结果,一个应用线程将被中断处理输出,以恢复其从该点开始的执行流。
事件循环
使用基于事件的风格,任务交接和线程中断发生在应用-操作系统的边界上。这种交换就是我们所知道的 事件循环.
Epoll仍然要运行一个线程来轮询所有的套接字,这本质上是一个阻塞性操作。这不是和线程池(池中只有一个线程)一样吗?
这是正确的,但我们(应用程序)不再这样做了。我们的应用程序是完全由事件驱动的。操作系统在处理这些低级别的活动方面非常有效,比如轮询套接字。
构建可组合的网络应用
不要建立网络单体。使用 比特来创建和组成解耦的软件组件--在你喜欢的框架中,如React或Node。构建可扩展和模块化的应用程序,提供强大和愉快的开发体验。
把你的团队带到 比特云来托管并共同协作开发组件,并作为一个团队加快、扩大和规范开发。尝试用设计系统 或微前端的可组合前端,或探索用服务器端组件的可组合后端。