短话长说系列之 —— JS的单线程

138 阅读3分钟

先说说单线程

众所周知,单线程模型指的是程序或进程在同一时间只能执行一个任务,程序将按顺序执行任务,每个任务必须在下一个任务开始前完成。这和多线程模型就形成了鲜明对比,后者可以同时处理多个任务。那么问题来了,我们的JavaScript为什么不直接采用多线程呢?

再谈历史背景

JavaScript最初是在1995年由Brendan Eich在短短十天内设计出来的(大佬!瑞思拜!)。当时的目标是创建一种轻量级的脚本语言,用于在Netscape浏览器中增强用户体验。

后面随着它的快速发展,各大浏览器厂商百花齐放,但是这就带来了浏览器实现的标准不同而产生的兼容性问题,为了解决这个问题,就有了后来的ECMAScript标准(其实这段不重要,和文章主题关系不大)。

为什么被设计为单线程?

在JavaScript初创时期,它主要是为了帮助开发者为网页(当时的网页大多是静态的,仅展示文本和图片)增加动态性和丰富的功能,比如处理用户输入,表单验证,动态更新HTML内容等。而单线程能够简化事件处理逻辑,因为在单线程环境中,开发者不需要担心多线程之间的同步问题。

试想,当两个线程同时尝试修改某个dom元素的场景,这可能会产生冲突和不可预测的结果。再者,多线程编程中的并发问题,如死锁,这类并发问题在JavaScript中是不需要考虑的。

那么这里就可以回答上面的问题(我们的JavaScript为什么不直接采用多线程呢?)了,虽然多线程能够同时处理多个任务,但是它也带来了进程管理和同步的额外开销,对于JavaScript这种主要用于用户界面交互的语言来说,单线程简化了这些复杂性。

单线程带来的问题

  • 阻塞问题:长时间运行的脚本可能会导致网页失去响应。如很多时候,前端开发同学都会注意不将体积较大的js文件直接放在网页头部进行引入,或者通过defer和async属性来进行优化。
  • 性能限制:单线程模型限制了JavaScript利用多核CPU的能力,无法充分释放硬件资源的能力来处理一些计算密集型任务。
  • 如何通过异步编程来解决阻塞问题,如事件循环,回调函数,promise,async/await等

JavaScript可以使用多线程吗?

答案是可以,HTML5提出的Web Workers标准,允许JavaScript脚本创建多个线程,它是一种在后台运行的JavaScript线程,通常我们会将一些繁重的计算或者I/O操作丢给它来执行。比如图像处理,如果放在主线程上执行,这将阻塞浏览器响应用户的交互。

在Node.j环境中,还可以使用child_process模块来创建新的进程来执行CPU密集型任务和运行其他的程序,这些进程可以并且执行,执行完以后通过事件的回调来与主进程通信。

总结

JavaScript采用单线程模型主要是为了简化浏览器中的事件处理和用户界面交互。这种设计避免了多线程编程中的复杂同步问题,但也带来了性能限制和阻塞问题。随着技术的发展,JavaScript通过异步编程和HTML5的Web Workers等技术解决了这些挑战,使其能够在保持单线程简洁性的同时,提高应用的性能和响应性。