何为进程
进程(Process) 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基。每个程序都需要自己的专属的内存空间,可以把这款空间简单的理解为进程;每个应用至少有一个进程,进程之间相互独立,即使通信也需要双方同意;
何为线程
线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
浏览器中的线程和进程
我们可以带着一个老生常谈的问题去思考:**为什么Javascript是单线程?**要更好地理解这个问题,我们需要首先了解浏览器的工作原理;浏览器是一个多进程多线程的应用程序;浏览器内部为了避免相互影响,会开启多进程,每个进程负责一个模块。这里核心了解的就是大致三个进程:浏览器进程、网络进程、渲染进程。同时每个进程中又会启动多个线程来处理中不同的任务;
- 浏览器进程
主要负责界面的交互、用户的交互、子进程的管理(其他进程的启动)
- 网络进程
负责加载网络资源
- 渲染进程
渲染进程启动后,会开启一个渲染主线程,用来执行HTML、CSS、JS;默认情况下是一个标签页一个渲染进程,以保证不同的标签页之间互补影响
由此可以理解到为什么Javascript是单线程的了,因为JavaScript作为浏览器脚本语言,运行环境主要是在浏览器的**渲染主线程;**其次JavaScript 的主要用途是与用户互动,以及操作 DOM。若以多线程的方式操作这些 DOM,则可能出现操作的冲突。假设有两个线程同时操作一个 DOM 元素,线程 1 要求浏览器删除 DOM,而线程 2 却要求修改 DOM 样式,这时浏览器就无法决定采用哪个线程的操作;所以种种原因造就了javaScript 在创造之初就是单线程的;
浏览器消息队列
渲染主线程是浏览器中最繁忙的线程,需要处理各种各样的任务;详情参考后续文章 《浏览器的渲染原理》;因为 JavaScript 是单线程的,在某一时刻内只能执行特定的一个任务,并且会阻塞其它任务执行。那么如何去调度这些任务呢?
答案就是 队列;浏览中通过队列的方式来调度这个任务,先进的任务先执行,后进的任务后执行,在同一时间只执行一个任务;
浏览器消息队列模型(EventLoop)
那么现在解释一下什么叫做事件循环
- 在最开始的时候,渲染主线程会进入一个无限的死循环
- 每一次循环都会检查消息队列中是否还有任务存在;如果有,就去第一个任务来运行,执行完后在取下一个任务来运行;如果消息队列里面空了,那就进入休眠状态,知道有新的任务被添加进来;
- 其他所有线程都可以随时向消息队列里面添加任务。新的任务都会添加到队列的末尾,在添加任务任务的时候如果主线程处于休眠状态,会被唤起来执行步骤2;
浏览器Message loop 源码
Js中同步和异步
JS中的同步很好理解,就是指代码按照顺序依次执行,每一行代码执行完成后才会执行下一行代码。渲染主线程处理消息队列中任务的方式就是一个同步执行,但是同步处理会阻塞代码的执行,直到前面的代码执行完成,后面的代码才会得到执行。 在渲染主线程中会有遇到一些无法立即执行的任务,比如:
- 计时完成后需要执行的任务:
setTimeout、setInterval
- 网络通信完成后需要执行的任务:
XHR、Fetch
- 用户操作后的需要执行的任务:
addEventListener
如果处理这些任务还是按照同步的方式去执行的话,会造成整个渲染主线程长时间处于阻塞状态,无法正常处理其他任务;
消息队列的特性:绝不阻塞。所以Javascript采用异步的方式处理这些无法立即执行的任务;
异步处理是指代码执行时不会阻塞后续代码的执行,同时会在后台执行一些其他的操作。在这些任务完成前,JavaScript 完全可以往下执行其他操作,当这些耗时的任务完成后则以回调的方式执行相应处理。
当然对于不可避免的耗时操作(如:繁重的运算,多重循环),HTML5 提出了Web Worker,它会在当前 JavaScript 的执行主线程中利用 Worker 类新开辟一个额外的线程来加载和运行特定的 JavaScript 文件,这个新的线程和 JavaScript 的主线程之间并不会互相影响和阻塞执行,而且在 Web Worker 中提供了这个新线程和 JavaScript 主线程之间数据交换的接口:postMessage 和 onMessage 事件。但在 HTML5 Web Worker 中是不能操作 DOM 的,任何需要操作 DOM 的任务都需要委托给 JavaScript 主线程来执行,所以虽然引入 HTML5 Web Worker,但仍然没有改线 JavaScript 单线程的本质。
任务和队列的优先级
任务没有优先级,在队列中先进显出,但是队列有优先级;
每个任务都有一个任务类型,同一个类型的任务必须在同一个队列,不同类型的任务可以属于不同的队列。再一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行
chrome中所有的任务类型如下:
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_TASK_TYPE_H_
#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_TASK_TYPE_H_
namespace blink {
// A list of task sources known to Blink according to the spec.
// This enum is used for a histogram and it should not be re-numbered.
//
// For the task type usage guideline, see https://bit.ly/2vMAsQ4
//
// When a new task type is created:
// * Set the new task type's value to "Next value"
// * Update kMaxValue to point to the new task type
// * Increment "Next value"
// * Update TaskTypes.md
//
// Next value: 87
enum class TaskType : unsigned char {
///
// Speced tasks should use one of the following task types
///
// Speced tasks and related internal tasks should be posted to one of
// the following task runners. These task runners may be throttled.
// This value is used as a default value in cases where TaskType
// isn't supported yet. Don't use outside platform/scheduler code.
kDeprecatedNone = 0,
// https://html.spec.whatwg.org/multipage/webappapis.html#generic-task-sources
//
// This task source is used for features that react to DOM manipulations, such
// as things that happen in a non-blocking fashion when an element is inserted
// into the document.
kDOMManipulation = 1,
// This task source is used for features that react to user interaction, for
// example keyboard or mouse input. Events sent in response to user input
// (e.g. click events) must be fired using tasks queued with the user
// interaction task source.
kUserInteraction = 2,
// TODO(altimin) Fix the networking task source related namings once it is
// clear how
// all loading tasks are annotated.
// This task source is used for features that trigger in response to network
// activity.
kNetworking = 3,
// This is a part of Networking task that should not be frozen when a page is
// frozen.
kNetworkingUnfreezable = 75,
// Tasks associated with loading that should also block rendering. Split off
// from kNetworkingUnfreezable so tasks such as image loading can be
// prioritized above rendering. Note that not all render-blocking resources
// use this queue (e.g., script load tasks are not put here).
kNetworkingUnfreezableRenderBlockingLoading = 83,
// This task source is used for control messages between kNetworking tasks.
kNetworkingControl = 4,
// Tasks used to run low priority scripts.
kLowPriorityScriptExecution = 81,
// This task source is used to queue calls to history.back() and similar APIs.
kHistoryTraversal = 5,
// https://html.spec.whatwg.org/multipage/embedded-content.html#the-embed-element
// This task source is used for the embed element setup steps.
kEmbed = 6,
// https://html.spec.whatwg.org/multipage/embedded-content.html#media-elements
// This task source is used for all tasks queued in the [Media elements]
// section and subsections of the spec unless explicitly specified otherwise.
kMediaElementEvent = 7,
// https://html.spec.whatwg.org/multipage/scripting.html#the-canvas-element
// This task source is used to invoke the result callback of
// HTMLCanvasElement.toBlob().
kCanvasBlobSerialization = 8,
// https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
// This task source is used when an algorithm requires a microtask to be
// queued.
kMicrotask = 9,
// https://html.spec.whatwg.org/multipage/webappapis.html#timers
// For tasks queued by setTimeout() or setInterval().
//
// Task nesting level is < 5 and timeout is zero.
kJavascriptTimerImmediate = 72,
// Task nesting level is < 5 and timeout is > 0.
kJavascriptTimerDelayedLowNesting = 73,
// Task nesting level is >= 5.
kJavascriptTimerDelayedHighNesting = 10,
// Note: The timeout is increased to be at least 4ms when the task nesting
// level is >= 5. Therefore, the timeout is necessarily > 0 for
// kJavascriptTimerDelayedHighNesting.
// https://html.spec.whatwg.org/multipage/comms.html#sse-processing-model
// This task source is used for any tasks that are queued by EventSource
// objects.
kRemoteEvent = 11,
// https://html.spec.whatwg.org/multipage/comms.html#feedback-from-the-protocol
// The task source for all tasks queued in the [WebSocket] section of the
// spec.
kWebSocket = 12,
// https://html.spec.whatwg.org/multipage/comms.html#web-messaging
// This task source is used for the tasks in cross-document messaging.
kPostedMessage = 13,
// https://html.spec.whatwg.org/multipage/comms.html#message-ports
kUnshippedPortMessage = 14,
// https://www.w3.org/TR/FileAPI/#blobreader-task-source
// This task source is used for all tasks queued in the FileAPI spec to read
// byte sequences associated with Blob and File objects.
kFileReading = 15,
// https://www.w3.org/TR/IndexedDB/#request-api
kDatabaseAccess = 16,
// https://w3c.github.io/presentation-api/#common-idioms
// This task source is used for all tasks in the Presentation API spec.
kPresentation = 17,
// https://www.w3.org/TR/2016/WD-generic-sensor-20160830/#sensor-task-source
// This task source is used for all tasks in the Sensor API spec.
kSensor = 18,
// https://w3c.github.io/performance-timeline/#performance-timeline
kPerformanceTimeline = 19,
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15
// This task source is used for all tasks in the WebGL spec.
kWebGL = 20,
// https://www.w3.org/TR/requestidlecallback/#start-an-event-loop-s-idle-period
kIdleTask = 21,
// Use MiscPlatformAPI for a task that is defined in the spec but is not yet
// associated with any specific task runner in the spec. MiscPlatformAPI is
// not encouraged for stable and matured APIs. The spec should define the task
// runner explicitly.
// The task runner may be throttled.
kMiscPlatformAPI = 22,
// Tasks used for DedicatedWorker's requestAnimationFrame.
kWorkerAnimation = 51,
// Obsolete.
// kNetworkingWithURLLoaderAnnotation = 50, (see crbug.com/860545)
// kExperimentalWebSchedulingUserInteraction = 53,
// kExperimentalWebSchedulingBestEffort = 54,
// https://drafts.csswg.org/css-font-loading/#task-source
kFontLoading = 56,
// https://w3c.github.io/manifest/#dfn-application-life-cycle-task-source
kApplicationLifeCycle = 57,
// https://wicg.github.io/background-fetch/#infrastructure
kBackgroundFetch = 58,
// https://www.w3.org/TR/permissions/
kPermission = 59,
// https://w3c.github.io/ServiceWorker/#dfn-client-message-queue
kServiceWorkerClientMessage = 60,
// https://w3c.github.io/web-locks/#web-locks-tasks-source
kWebLocks = 66,
// Task type used for the Prioritized Task Scheduling API
// (https://wicg.github.io/scheduling-apis/#the-posted-task-task-source).
// This task type should not be passed directly to
// FrameScheduler::GetTaskRunner(); it is used indirectly by
// WebSchedulingTaskQueues.
kWebSchedulingPostedTask = 67,
// https://w3c.github.io/screen-wake-lock/#dfn-screen-wake-lock-task-source
kWakeLock = 76,
// https://storage.spec.whatwg.org/#storage-task-source
kStorage = 82,
// https://www.w3.org/TR/clipboard-apis/#clipboard-task-source
kClipboard = 85,
// https://www.w3.org/TR/webnn/#ml-task-source
kMachineLearning = 86,
///
// Not-speced tasks should use one of the following task types
///
// The default task type. The task may be throttled or paused.
kInternalDefault = 23,
// Tasks used for all tasks associated with loading page content.
kInternalLoading = 24,
// Tasks for tests or mock objects.
kInternalTest = 26,
// Tasks that are posting back the result from the WebCrypto task runner to
// the Blink thread that initiated the call and holds the Promise. Tasks with
// this type are posted by:
// * //components/webcrypto
kInternalWebCrypto = 27,
// Tasks to execute media-related things like logging or playback. Tasks with
// this type are mainly posted by:
// * //content/renderer/media
// * //media
kInternalMedia = 29,
// Tasks to execute things for real-time media processing like recording. If a
// task touches MediaStreamTracks, associated sources/sinks, and Web Audio,
// this task type should be used.
// Tasks with this type are mainly posted by:
// * //content/renderer/media
// * //media
// * blink/renderer/modules/webaudio
// * blink/public/platform/audio
kInternalMediaRealTime = 30,
// Tasks related to user interaction like clicking or inputting texts.
kInternalUserInteraction = 32,
// Tasks related to the inspector.
kInternalInspector = 33,
// Obsolete.
// kInternalWorker = 36,
// Translation task that freezes when the frame is not visible.
kInternalTranslation = 55,
// Tasks used at IntersectionObserver.
kInternalIntersectionObserver = 44,
// Task used for ContentCapture.
kInternalContentCapture = 61,
// Navigation tasks and tasks which have to run in order with them, including
// legacy IPCs and channel associated interfaces.
// Note that the ordering between tasks related to different frames is not
// always guaranteed - tasks belonging to different frames can be reordered
// when one of the frames is frozen.
// Note: all AssociatedRemotes/AssociatedReceivers should use this task type.
kInternalNavigationAssociated = 63,
// Tasks which should run when the frame is frozen, but otherwise should run
// in order with other legacy IPC and channel-associated interfaces.
// Only tasks related to unfreezing itself should run here, the majority of
// the tasks
// should use kInternalNavigationAssociated instead.
kInternalNavigationAssociatedUnfreezable = 64,
// Task used to split a script loading task for cooperative scheduling
kInternalContinueScriptLoading = 65,
// Tasks used to control frame lifecycle - they should run even when the frame
// is frozen.
kInternalFrameLifecycleControl = 68,
// Tasks used for find-in-page.
kInternalFindInPage = 70,
// Tasks that come in on the HighPriorityLocalFrame interface.
kInternalHighPriorityLocalFrame = 71,
// Tasks that are should use input priority task queue/runner.
kInternalInputBlocking = 77,
// Tasks related to the WebGPU API
kWebGPU = 78,
// Cross-process PostMessage IPCs that are deferred in the current task.
kInternalPostMessageForwarding = 79,
// Tasks related to renderer-initiated navigation cancellation.
kInternalNavigationCancellation = 80,
///
// The following task types are only for thread-local queues.
///
// The following task types are internal-use only, escpecially for annotations
// like UMA of per-thread task queues. Do not specify these task types when to
// get a task queue/runner.
kMainThreadTaskQueueV8 = 37,
kMainThreadTaskQueueV8LowPriority = 84,
kMainThreadTaskQueueCompositor = 38,
kMainThreadTaskQueueDefault = 39,
kMainThreadTaskQueueInput = 40,
kMainThreadTaskQueueIdle = 41,
// Removed:
// kMainThreadTaskQueueIPC = 42,
kMainThreadTaskQueueControl = 43,
// Removed:
// kMainThreadTaskQueueCleanup = 52,
kMainThreadTaskQueueMemoryPurge = 62,
kMainThreadTaskQueueNonWaking = 69,
kMainThreadTaskQueueIPCTracking = 74,
kCompositorThreadTaskQueueDefault = 45,
kCompositorThreadTaskQueueInput = 49,
kWorkerThreadTaskQueueDefault = 46,
kWorkerThreadTaskQueueV8 = 47,
kWorkerThreadTaskQueueCompositor = 48,
kMaxValue = kMachineLearning,
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_TASK_TYPE_H_
目前浏览器至少需要包含三个队列:
- 微队列:用户存放需要最快执行的队列,优先级最高,浏览器中必须存在这个微队列
添加任务到微队列的的主要方式: Promise、MutationObserver
- 交互队列:用于存放用户交互后的产生的事件处理任务,优先级高
- 延时队列:用于存放计时器到期后的任务,优先级中等
接下来分析一个例子来感受一下消息队列
先同步执行整个代码块,首先会先执行到fn1
函数声明可以先忽略;然后会把setTimeout
放到延时进程中去执行,再加上只需要等待0ms,所有setTimeout
的回调函数会立刻被放到延时队列中,但不会执行;紧接着开始执行到Promise.resolve()
,此时相当于把then的回调函数放到为队列中;最后执行console.log("4")
;
同步代码执行完毕后,各个消息队列的状态:
接下来按照之前说的队列优先级,开始执行微队列中的任务;在fn1
这个函数中,首先执行了console.log("1")
;接下来执行Promise.resolve()
,按照刚才说的就相当于把then函数的回调放到微队列中;所以在fn1
函数执行完毕后,此时的队列状态为
此时微队列中还存在任务,所有继续从微队列中拿出第一项任务开始执行 console.log("2")
,执行完毕后此时微队列中全部执行完毕,接下来开始执行下一个优先级的队列,最后执行延时队列中的 console.log("3")
;所以按照我们的分析,最后这段代码执行后输出的结果为:4 1 2 3
;
最后在终端验证下输出的结果:
总结:
- JavaScript 是单线程的,同一时刻只能执行特定的任务,而浏览器是多线程的。
- 异步任务(各种浏览器事件、定时器等)都是先添加到相应的任务队列中。当 JavaScript 主线程为空时,就会按照一定的优先级读取消息队列的第一个任务,然后执行。