为啥 JavaScript 在普通浏览器中没有自己的线程?
Posted
技术标签:
【中文标题】为啥 JavaScript 在普通浏览器中没有自己的线程?【英文标题】:Why doesn't JavaScript get its own thread in common browsers?为什么 JavaScript 在普通浏览器中没有自己的线程? 【发布时间】:2015-09-05 03:00:45 【问题描述】:javascript 不是多线程的还不够,显然 JavaScript 甚至没有自己的线程,而是与大量其他东西共享一个线程。即使在大多数现代浏览器中,JavaScript 通常也与绘画、更新样式和处理用户操作处于同一队列中。
为什么会这样?
根据我的经验,如果 JavaScript 在自己的线程上运行,仅靠 JS 不阻塞 UI 渲染或解放复杂或有限的消息队列优化样板(是的,你也是 webworkers!),就可以获得极大改善的用户体验。开发人员必须编写自己的代码,以确保 UI 在任何地方都能响应。
我有兴趣了解导致这种看似不幸的设计决策的动机,从软件架构的角度来看,是否有令人信服的理由?
【问题讨论】:
从 qt 到 gtk 到 nextStep 到 Windows API 的所有其他 UI 库/框架都是单线程的,这并非偶然。制作多线程 UI 很困难,制作比单线程 UI 慢的多线程 UI 非常容易,因为 UI 代码包含大量全局状态。事实上,几乎总是如此。 Javascript,虽然它已经成长为做其他事情,但只是被设计成一个 UI 操作系统。使其成为多线程很可能会使浏览器变慢并容易出现死锁。 历史轶事:旧的“经典”MacOS,在 OSX 之前,完全是单线程的(根据 Unix 对线程的定义)。操作系统没有虚拟内存(因此所有程序与操作系统共享一个大地址空间)。操作系统无法控制程序(所有程序都会阻止操作系统本身 - 操作系统依赖于定期产生的程序,这有点像在 js 中执行 setTimeout 或 setInterval)。这使得 GUI 响应速度非常快,但任何程序中的一个错误都会挂起整个操作系统 【参考方案1】:用户操作需要 JS 事件处理程序的参与
用户操作可以触发参与并可能影响用户操作的 Javascript 事件(点击、焦点事件、键事件等),因此很明显,在处理用户操作时单个 JS 线程无法执行,因为,如果是这样,则 JS 线程无法参与用户操作,因为它已经在做其他事情。因此,在 JS 线程可以参与该过程之前,浏览器不会处理默认用户操作。
渲染
渲染更复杂。一个典型的 DOM 修改序列是这样的:1) 由 JS 修改的 DOM,布局标记为脏,2) JS 线程执行完毕,因此浏览器现在知道 JS 完成了对 DOM 的修改,3) 浏览器执行布局以重新布局更改的 DOM,4 ) 浏览器根据需要绘制屏幕。
步骤 2) 在这里很重要。如果浏览器在每次 JS DOM 修改后都进行新的布局和屏幕绘制,那么如果 JS 实际上要进行大量 DOM 修改,那么整个过程可能会非常低效。另外,还会存在线程同步问题,因为如果您在浏览器尝试重新布局和重绘的同时让 JS 修改 DOM,则您必须同步该活动(例如,阻止某人以便操作可以在没有底层数据正在被另一个线程更改)。
仅供参考,有一些变通方法可用于强制重新布局或从 JS 代码中强制重新绘制(不完全是您所要求的,但在某些情况下很有用)。
多线程访问 DOM 真的很复杂
DOM 本质上是一个大的共享数据结构。浏览器在解析页面时构造它。然后加载脚本和各种JS事件就有机会修改了。
如果你突然有多个可以访问 DOM 的 JS 线程同时运行,你就会遇到一个非常复杂的问题。您将如何同步访问?您甚至无法编写涉及在页面中查找 DOM 对象然后对其进行修改的最基本的 DOM 操作,因为这不是原子操作。在您找到 DOM 对象和进行修改之间,DOM 可能会发生变化。相反,您可能必须在 DOM 中的至少一个子树上获得一个锁,以防止它在您操作或搜索它时被其他线程更改。然后,在进行修改之后,您必须释放锁并从代码中释放有关 DOM 状态的任何信息(因为一旦您释放锁,其他线程可能会更改它)。而且,如果你没有正确地做事,你最终可能会出现死锁或各种令人讨厌的错误。实际上,您必须将 DOM 视为并发的多用户数据存储。这将是一个复杂得多的编程模型。
避免复杂性
“单线程 JS”设计决策中有一个统一的主题。 保持简单。不需要了解多线程环境和线程同步工具以及调试多线程即可编写可靠、可靠的浏览器 Javascript。
浏览器 JavaScript 是一个成功的平台的一个原因是因为它对所有级别的开发人员都非常容易访问,并且相对容易学习和编写可靠的代码。虽然随着时间的推移,浏览器 JS 可能会获得更高级的功能(就像我们使用 WebWorkers 获得的那样),但您可以绝对确定这些将以一种简单的事情保持简单的方式完成,而更高级的事情可以由更高级的开发人员完成,但没有打破现在让事情变得简单的任何事情。
仅供参考,我已经在 node.js 中编写了一个多用户 Web 服务器应用程序,我经常惊讶于由于 nodejs Javascript 的单线程特性,服务器设计的复杂性降低了多少。是的,有些东西写起来更痛苦(学习编写大量异步代码的承诺),但是令人惊叹的是,您的 JS 代码永远不会被另一个请求中断的简化假设大大简化了设计、测试并减少了很难找到和修复并发设计和编码总是充满的错误。
讨论
当然,第一个问题可以通过允许用户操作事件处理程序在自己的线程中运行来解决,这样它们就可以随时发生。但是,你马上就有了多线程 Javascript,现在需要一个全新的 JS 基础设施来进行线程同步和全新的错误类。浏览器 Javascript 的设计者一直决定不打开那个框。
如果需要,可以改进渲染问题,但会使浏览器代码复杂化。您必须发明一些方法来猜测正在运行的 JS 代码何时似乎不再更改 DOM(可能有一些毫秒数没有更多更改),因为您必须避免立即进行重新布局和屏幕绘制每次 DOM 更改。如果浏览器这样做,一些 JS 操作会变得比现在慢 100 倍(100 倍是一个疯狂的猜测,但关键是它们会慢很多)。而且,您必须在布局、绘画和 JS DOM 修改之间实现线程同步,这是可行的,但很复杂,需要大量的工作和浏览器实现错误的沃土。而且,您必须决定在重新布局或重绘的过程中要做什么,并且 JS 线程会进行 DOM 修改(没有一个答案很好)。
【讨论】:
此外,跨线程访问 DOM 将彻底改变一切。即使是一个简单的应用程序,JavaScript 也会变得更加复杂和难以设计和调试。 能否请您指出那些从 JS 代码中强制重新布局/重绘的变通办法? @user511287 - 见When does the DOM repaint during Javascript routines?。 @jfriend00 再次感谢!确实是我在这里得到的最相关和最有用的答案之一以上是关于为啥 JavaScript 在普通浏览器中没有自己的线程?的主要内容,如果未能解决你的问题,请参考以下文章