为什么线程如此昂贵,是事件驱动的非阻塞IO是更好的基准

Aur*_*lia 7 multithreading event-driven-design event-driven

最近,我开始学习Node.js的,对自己的无阻塞IO和令人难以置信的速度见长的V8之上的JavaScript库.

据我了解,节点不等待IO的响应,但运行的事件循环(类似于游戏循环),保持检查未完成的操作,并继续/尽快IO响应完成它们.节点性能与Apache的HTTPD与节点是显著更快,同时使用更少的内存.

现在,如果你了解了Apache,你了解它每个用户使用1个线程,这理应减缓下来显著,这是出现在我的问题,其中:

如果将线程与事件循环内部节点进行比较,则会开始看到相似之处:两者都是等待资源响应的未完成进程的抽象,都检查操作是否定期进行,然后不占用CPU占用了一段时间(至少我认为一个好的阻塞API在重新检查之前会休眠几毫秒).

现在哪个突出的,关键的差异让线程变得更糟?

Joh*_*rak 10

这里的区别在于上下文切换.操作系统交换线程需要:

  • 保存指令指针(由CPU完成)
  • 保存CPU寄存器(如果线程已进行阻塞调用,则可能不是必需的,但如果被抢占则是必需的)
  • 交换调用栈.即使堆栈驻留在相同的虚拟内存空间中,这至少是一次写入,一些读取甚至适用于微线程(光纤).
  • 在交换到不同进程的情况下,交换到内核模式,更新虚拟内存表并返回到用户模式.

在事件队列的情况下:

  • 国家更新.无论如何都需要这样做.
  • 事件处理程序返回.不会交换调用堆栈,而是弹出当前调用堆栈.
  • 检查事件队列是否有待处理的请求.只有在没有待处理请求时,应用程序才会等待.这可以通过重复睡眠(如OP建议)或(更好)通过对事件队列进行阻塞调用来完成.如果事件队列(例如一组TCP套接字)由OS管理,则OS负责在新事件上通知应用程序(套接字可以接受更多数据).

如果服务器是高度加载的,则事件队列的唯一开销是处理程序返回,读取队列和处理程序调用.在线程方法中,交换线程会产生额外的开销.

此外,如PST所述,线程方法引入了锁定的需要.锁定本身很便宜,但是等待某个其他线程释放资源需要额外的上下文切换,因为等待线程无法继续.甚至可以交换一个线程来获取一个锁,只是为了在几个时钟周期之后被换出,因为它也需要锁定另一个资源.比较操作系统(读取胎面队列和交换调用堆栈,至少)完成了多少工作(通过调用返回并进行另一次调用).