By the end of this article you will have learned the following:
- The difference between process and thread: the concept of process and thread and single thread and multi-thread;
- Browser kernel knowledge: GUI rendering thread, JavaScript engine thread, event-triggering thread, etc.
- What Web Workers are: limitations and capabilities of Web Workers and how the main thread communicates with Web Workers;
- Classification of Web Workers: Dedicated Worker, Shared Worker and Service Workers;
- Web Workers API: Worker constructor and how to observe Dedicated Workers.
Read Po’s recent popular articles (thanks to Digg friends for their encouragement and support 🌹🌹🌹) :
- 1.2 W word | great TypeScript introductory tutorial (1160 + 👍)
- Top 10 TS Projects (670+ 👍)
- Understanding TypeScript generics and applications (7.8K words) (530+ 👍)
- Front-end storage in addition to localStorage what else (396+ 👍)
- Image processing need not worry, give you ten small helpers (405+ 👍)
Now we start to get into the topic. In order to make you better understand and master Web Workers, before formally introducing Web Workers, we will first introduce some basic knowledge related to Web Workers.
First, the difference between process and thread
Before we introduce the concept of process and thread, let’s take a look at the relationship between process and thread figuratively:
As shown in the figure above, a process is a factory with independent resources, threads are workers in the factory, multiple workers collaborate to complete tasks, and workers share resources within the factory, such as a canteen or restaurant within the factory. In addition, factories (processes) are independent of each other. To give you a more intuitive understanding of the difference between a process and a thread, let’s look at another diagram:
As you can see from the figure above, the operating system allocates independent memory space for each process. A process consists of one or more threads, and each thread under the same process shares the memory space of the program. The difference between a process and a thread has been clarified by the previous two pictures. Is this the case? Here we turn on the macOS activity monitor to see the status of all the processes at the time of writing this article:
As can be seen from the figure above, the commonly used software, such as wechat and Sogou input method, is an independent process with different PID (process ID), and each process in the figure has multiple threads. Take wechat process for example, it has “36” threads. So what are processes and threads? Let’s introduce the concept of processes and threads.
1.1 Concept of process
A process (English: process) is a program that is already running in a computer. Processes were once the basic operating unit of time-sharing systems. In process-oriented systems (such as early UNIX, Linux 2.4 and earlier), a process is the basic execution entity of a program; “In thread-oriented systems (such as most modern operating systems, Linux 2.6 and later), the process itself is not the basic unit of operation, but the container for the thread.”
The program itself is only a description of instructions, data and organizational form, and the process is the real running instance of the program. Several processes may be associated with the same program, and each process may run independently, either synchronously or asynchronously. Modern computer systems can load multiple programs into memory as processes at the same time, and use time sharing (or TDM) to give the impression of running simultaneously on a single processor.
1.2 The concept of threads
A thread (English: thread) is the smallest unit in which an operating system can schedule operations. In most cases, it is contained within a process and is the actual operational unit of the process. “A thread is a single sequential flow of control in a process, where multiple threads can be concurrent, each performing a different task in parallel.”
Threads are the basic unit of independent scheduling and dispatch. Threads Kernel threads that can be scheduled for the operating system kernel, such as Win32 threads; User threads scheduled by user processes, such as POSIX threads on Linux. Or it can be mixed by the kernel and user processes, such as Windows 7 threads.
“Multiple threads in the same process share all system resources in that process, such as virtual address space, file descriptors, signal processing, and so on.” But multiple threads in the same process have their own call stack, their own register context, and their own thread-local storage. A process can have many threads, each performing different tasks in parallel.
1.3 Single-thread vs. multi-thread
If a process has only one thread, we call it single-threaded. Single thread in program execution, the path of the program in sequential order, the first must be processed before the next can be executed. Advantages of single-threaded processing: Synchronous applications are easier to develop, but are generally less efficient than multithreaded applications because they need to start a new task after the previous one has completed.
If the synchronization task takes longer than expected to complete, the application may not respond. To address this problem, consider multithreading, that is, using multiple threads in a process so that you can handle multiple tasks.
JavaScript, as Web developers are familiar with, runs in a browser and is single-threaded, one JavaScript thread per window. Since it is single-threaded, only certain code can be executed at a given moment, and the rest of the code is blocked.
❝
JS in fact is no thread concept, the so-called single thread is only relative to multithreading. JS is not designed to take these into account. We call it “single thread” because JS does not have the feature of parallel task processing. How to prove that JavaScript is single threaded? Answer by @Yundan
❞
In fact, in the browser kernel (rendering process) in addition to JavaScript engine threads, there are GUI rendering threads, event trigger threads, timing trigger threads, etc. So for the browser rendering process, it’s multithreaded. Let’s take a quick look at the browser kernel.
Second, browser kernel
“The core Rendering Engine is the browser’s Rendering Engine, but we usually refer to it as the browser core.” It mainly consists of the following threads:
Let’s take a look at each thread in the renderer process separately.
2.1 GUI rendering threads
GUI rendering threads are responsible for rendering browser interfaces, parsing HTML, CSS, building DOM trees and RenderObject trees, layout and drawing, etc. This thread executes when the interface needs to be repainted or when some operation causes Reflow.
2.2 JavaScript Engine threads
JavaScript engine threads are responsible for parsing JavaScript scripts and running the associated code. The JavaScript engine waits for tasks to arrive in the task queue and then processes them, and there is only one JavaScript thread running a JavaScript program on a Tab page (Renderer process) at any given time.
Note that the GUI rendering thread is mutually exclusive with the JavaScript engine thread, so if the JavaScript execution takes too long, it will render the page incoherently and cause the page rendering to block.
2.3 Event triggering thread
When an event is triggered, the thread adds the event to the end of the queue, waiting for the JavaScript engine to process it. These events can be currently executing blocks of code, such as scheduled tasks, or other threads from the browser kernel, such as mouse clicks, AJAX asynchronous requests, etc., but because the JavaScript engine is single-threaded, all of these events have to be queued up for the JavaScript engine to process.
2.4 Timing trigger Thread
Browser timing counters are not counted by JavaScript engines, because JavaScript engines are single-threaded and blocking threads affect timing accuracy, so it is more reasonable to use separate threads to timing and trigger timing. The setInterval and setTimeout that we use in daily development are in this thread.
2.5 Http Asynchronous Request threads
If a callback function is set, the asynchronous thread generates the state change event and places it in the JavaScript engine’s processing queue to wait for processing when the XMLHttpRequest is detected after the connection by opening a new thread through the browser.
As we already know, because the JavaScript engine and the GUI rendering thread are mutually exclusive, if the JavaScript engine performs computationally intensive or high-latency tasks, the GUI rendering thread can be blocked or slowed down. So how to solve this problem? Heh heh, of course, is using the protagonist of this article – Web Workers.
Iii. What are Web Workers
Web workers are part of the HTML5 standard, which defines a set of apis that allow a JavaScript program to run in a thread other than the main thread. The function of Web Worker is to create a multithreaded environment for JavaScript, allowing the main thread to create Worker threads and assign some tasks to the latter to run.
While the main thread is running, the Worker thread is running in the background without interfering with each other. Wait until the Worker thread completes the calculation and returns the result to the main thread. The advantage of this is that some computationally intensive or high-latency tasks can be handled in a separate thread, allowing the main thread (usually the UI thread) not to be blocked or slowed down.
(photo: https://thecodersblog.com/web-worker-and-implementation/)
3.1 Limitations and capabilities of Web Workers
In general, you can run arbitrary code in Worker threads, but note some exceptions, such as “manipulating DOM elements directly in Worker threads, or using certain methods and attributes in window objects.” Most window object methods and properties are available, including WebSockets, and Data storage mechanisms such as IndexedDB and the Unique Data Store API in FireFox OS.
Using the Blink rendering engine used by Chrome and Opera as an example, we introduce the common APIs supported by Web workers in this rendering engine:
- Cache: The Cache interface provides a storage mechanism for cached pairs of Request/Response objects, for example, as part of the ServiceWorker life cycle.
- CustomEvent: Used to create custom events.
- Fetch: The Fetch API provides an interface to Fetch resources (including cross-domain requests). Anyone who has ever worked with XMLHttpRequest should be able to get started, and the new API offers a much more powerful and flexible set of features.
- Promise: The Promise object represents events that will happen in the future and is used to deliver messages about asynchronous operations.
- FileReader: The FileReader object allows a Web application to asynchronously read the contents of a File (or raw data buffer) stored on the user’s computer, using a File or Blob object to specify which File or data to read.
- IndexedDB: IndexedDB is an underlying API for clients to store large amounts of structured data, including file/binary large objects (BloBs).
- WebSocket: The WebSocket object provides an API for creating and managing WebSocket connections and for sending and receiving data over that connection.
- XMLHttpRequest: The XMLHttpRequest (XHR) object is used to interact with the server. With XMLHttpRequest, you can request a specific URL to retrieve data without refreshing the page. This allows the web page to update parts of the page without affecting the user’s actions.
See Functions and classes available to Workers for more information.
3.2 Communication between the main thread and Web Workers
The main thread and Worker threads send messages to each other using the postMessage() method and receive messages through the onMessage event handler. Data is interacted by passing copies rather than sharing data directly. The interaction between the main thread and Worker thread is shown in the figure below:
(photo: https://viblo.asia/p/simple-web-workers-workflow-with-webpack-3P0lPkobZox)
In addition, the Worker can access the network via XMLHttpRequest, but the responseXML and channel properties of the XMLHttpRequest object will always have null values.
Iv. Classification of Web Workers
Two types of Worker threads are defined in Web Worker specification, which are Dedicated thread Dedicated Worker and Shared thread Shared Worker respectively. Dedicated Worker can only be used by one page. Shared workers can be Shared by multiple pages.
4.1 Dedicated Worker
A dedicated Worker can only be used by the script that generates it, and its browser support is as follows:
(Photo credit: [https://caniuse.com/#search=Web%20Workers](https://caniuse.com/#search=Web Workers))
It should be noted that due to the same-origin limitation of Web workers, the local server needs to be started before local debugging or running the following example. When the page is opened directly using the file:// protocol, the following exceptions will be thrown:
Uncaught DOMException: Failed to construct 'Worker':
Script at 'file:///**/*.js' cannot be accessed from origin 'null'.
Copy the code
Dedicated Thread Dedicated Worker: Ping/Pong
“index.html“
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
<title>Dedicated Worker -- Ping/Pong</title> </head> <body> <h3>Dedicated Worker -- Ping/Pong</h3> <script> if (window.Worker) { let worker = new Worker("dw-ping-pong.js"); worker.onmessage = (e) = > console.log(`Main: Received message - ${e.data}`); worker.postMessage("PING"); } else { console.log("Whoo-hoo, Web workers are not supported"); } </script> </body> </html> Copy the code
“dw-ping-pong.js“
onmessage = (e) = > {
console.log(`Worker: Received message - ${e.data}`);
postMessage("PONG");
}
Copy the code
After the above code runs successfully, the browser console prints the following:
Worker: Received message - PING
Main: Received message - PONG
Copy the code
Each Web Worker can create its own child Worker, which allows us to spread tasks across multiple threads. Creating child workers is also very simple. Let’s look at an example.
Dedicated Sub Worker: Ping/Pong
“index.html“
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
<title>Dedicated Sub Worker -- Ping/Pong</title> </head> <body> <h3>Dedicated Sub Worker -- Ping/Pong</h3> <script> if (window.Worker) { let worker = new Worker("dw-ping-pong.js"); worker.onmessage = (e) = > console.log(`Main: Received message - ${e.data}`); worker.postMessage("PING"); } else { console.log("Whoo-hoo, Web workers are not supported"); } </script> </body> </html> Copy the code
“dw-ping-pong.js“
onmessage = (e) = > {
console.log(`Worker: Received message - ${e.data}`);
setTimeout((a)= > {
let worker = new Worker("dw-sub-ping-pong.js");
worker.onmessage = (e) = > console.log(`Worker: Received from sub worker - ${e.data}`);
worker.postMessage("PING"); }, 1000); postMessage("PONG"); }; Copy the code
“dw-sub-ping-pong.js“
onmessage = (e) = > {
console.log(`Sub Worker: Received message - ${e.data}`);
postMessage("PONG");
};
Copy the code
After the above code runs successfully, the browser console prints the following:
Worker: Received message - PING
Main: Received message - PONG
Sub Worker: Received message - PING
Received from sub worker - PONG
Copy the code
4.1.3 Dedicated Worker: importScripts
In Web workers, we can also use the importScripts method to synchronously import one or more scripts into the scope of the Web Worker. Again, let’s take an example.
“index.html“
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
<title>Dedicated thread Dedicated Worker -- importScripts</title> </head> <body> <h3>Dedicated Worker -- importScripts</h3> <script> let worker = new Worker("worker.js"); worker.onmessage = (e) = > console.log(`Main: Received kebab case message - ${e.data}`); worker.postMessage( "Hello, My name is semlinker." ); </script> </body> </html> Copy the code
“worker.js“
importScripts("https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.15/lodash.min.js");
onmessage = ({ data }) = > {
postMessage(_.kebabCase(data));
};
Copy the code
After the above code runs successfully, the browser console prints the following:
Main: Received kebab case message - hello-my-name-is-semlinker
Copy the code
4.1.4 Dedicated Worker: Inline-worker
In the previous examples, we used external Worker scripts to create Web Worker objects. You can also create Web workers using Blob urls or Data urls, which are also called Inline workers.
“1. Use the Blob URL to create an Inline Worker“
Blob URL/Object URL is a pseudo-protocol that allows Blob and File objects to be used as URL sources for images, links to download binary data, etc. In the browser, we create Blob urls using the url.createObjecturl method, which takes a Blob object and creates a unique URL for it of the form Blob :
/
, as shown in the following example:
blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641
Copy the code
The browser stores a URL → Blob mapping internally for each URL generated through url.createObjecturl. Therefore, such urls are short, but can access bloBs. The generated URL is valid only if the current document is open. It allows references to blobs in , , but if the Blob URL you visit no longer exists, you will get a 404 error from your browser.
const url = URL.createObjectURL(
new Blob([`postMessage("Dedicated Worker created by Blob")`])
);
let worker = new Worker(url);
worker.onmessage = (e) = > console.log(`Main: Received message - ${e.data}`); Copy the code
In addition to using strings to create Worker scripts dynamically in the code, you can also embed the script label of the type javascript/ Worker in the page, as shown below:
<script id="myWorker" type="javascript/worker">
self['onmessage'] = function(event) {
postMessage('Hello, ' + event.data.name + '! ');
};
</script>
Copy the code
The Blob API and createObjectURL API are used to create the Web Worker:
<script>
let workerScript = document.querySelector('#myWorker').textContent;
let blob = new Blob(workerScript, {type: "text/javascript"});
let worker = new Worker(URL.createObjectURL(blob));
</script>
Copy the code
“2. Use the Data URL to create an Inline Worker“
Data URLs are made up of four parts: a prefix (Data:), a MIME type that indicates the Data type, an optional Base64 tag if it’s not text, and the Data itself:
data:[<mediatype>][;base64],<data>
Copy the code
Mediatype is a string of MIME type, such as “image/ JPEG “for A JPEG image file. If omitted, the default is text/plain; Charset = US – ASCII. If the data is of text type, you can embed the text directly (using the appropriate entity character or escape character, depending on the document type). For binary data, you can base64 encode the data and then embed it.
const url = `data:application/javascript,The ${encodeURIComponent(
`postMessage("Dedicated Worker created by Data URL")`
)}`;
let worker = new Worker(url);
worker.onmessage = (e) = > console.log(`Main: Received message - ${e.data}`); Copy the code
4.2 Shared Worker
A shared Worker is a special type of Worker that can be accessed by multiple browsing contexts, such as multiple Windows, IFrames and Workers, but these browsing contexts must be of the same origin. They have different scopes than dedicated workers. The browser support is as follows:
(Photo credit: [https://caniuse.com/#search=Web%20Workers](https://caniuse.com/#search=Web Workers))
Different from regular workers, first we need to use the onConnect method to wait for the connection, and then we get a port, which is the connection between us and the window.
4.2.1 Shared Thread Shared Worker: Likes counter
“index.html“
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
<title>Shared threads Shared workers</title> </head> <body> <h3>Bob: Shared threads Shared workers</h3> <button id="likeBtn">give a like</button> <p>A bao elder brother has harvested altogether<span id="likedCount">0</span>A 👍</p> <script> let likes = 0; let likeBtn = document.querySelector("#likeBtn"); let likedCountEl = document.querySelector("#likedCount"); let worker = new SharedWorker("shared-worker.js"); worker.port.start(); likeBtn.addEventListener("click".function () { worker.port.postMessage("like"); }); worker.port.onmessage = function (val) { likedCountEl.innerHTML = val.data; }; </script> </body> </html> Copy the code
“shared-worker.js“
let a = Awesome!;
console.log("shared-worker");
onconnect = function (e) {
var port = e.ports[0];
port.onmessage = function () { port.postMessage(a++); }; }; Copy the code
The Shared Worker’s sample page has a “like” button that increases the number of likes by 1 each time it is clicked. First you open a new window and click a few times. Then open another window and click again. You will see that the number of likes displayed on the current page is based on the number of likes displayed on the previous page.
4.2.2 Debugging Shared Workers
In the actual project development process, if you need to debug scripts in Shared Workers, you can use Chrome ://inspect to debug, as shown in the figure below:
4.3 the Service Workers
Service Workers essentially act as a proxy server between the Web application and the browser, and can also act as a proxy between the browser and the network when the network is available. They are designed, among other things, to enable the creation of an effective offline experience, intercept network requests and take appropriate action based on whether the network is available and whether updated resources reside on the server.
(photo: https://www.pavlompas.com/blog/web-workers-vs-service-workers-vs-worklets)
The browser support of Service workers is as follows:
Since Service workers are not the focus of this paper, I will not introduce them here. If you are interested, please know by yourself. Let’s start with the Web Workers API.
Web Workers API
The Worker() constructor creates a Worker object that executes the specified URL script. This script must comply with the same origin policy. If the same origin policy is violated, a DOMException of type SECURITY_ERR is thrown.
5.1 Worker constructor
The Worker constructor syntax is:
const myWorker = new Worker(aURL, options);
Copy the code
Related parameters are described as follows:
- AURL: is the URL of a script that DOMString represents that the worker will execute. It must comply with the same origin policy.
- Options: Objects that contain option properties that can be set when an object instance is created. The available properties are as follows:
- Type: DOMString value used to specify the Worker type. The value can be classic or Module. If not specified, the default classic is used.
- Credentials: Used to specify the DOMString value of worker credentials. The value can be omit, SAME-origin, or include. If not specified, or if type is classic, the default value omit will be used (no credentials required).
- Name: in the case of DedicatedWorkerGlobalScope, used to show the Worker of the scope of a DOMString value, mainly used for debugging purposes.
Note that the following exceptions may occur when creating a Web Worker:
- A SecurityError is thrown when Document is not allowed to start the worker. For example, if the supplied aURL has a syntax error or conflicts with the same origin policy (cross-domain access).
- If the worker’s MIME type is incorrect, a NetworkError exception will be thrown. The WORKER’s MIME type must be
text/javascript
. - If the aURL cannot be parsed (malformed), a SyntaxError is thrown.
“The sample“
const worker = new Worker("task.js");
Copy the code
When we call the Worker constructor, we return a Worker thread object for the main thread to manipulate the Worker. The Worker thread object has the following properties and methods:
- Worker.onerror: Specifies a listener for error events.
- Worker. onMessage: Specifies the listener function for the message event
Event.data
Attribute. - Worker. onMessageError: Specifies a listener function for messageError events. This event is emitted when the sent data cannot be serialized into a string.
- Worker.postmessage () : sends a message to the Worker thread.
- Worker.terminate() : Terminates the Worker thread immediately.
5.2 Dedicated Worker Example
A Dedicated Worker is a Dedicated Worker.
“index.html“
<! DOCTYPE html><html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dedicated Worker Demo</title> </head> <body> <h3>Dedicated Worker Demo</h3> <script> const worker = new Worker("task.js"); worker.postMessage({ id: 666, msg: "Hello Semlinker", }); worker.onmessage = function (message) { let data = message.data; console.log(`Main: Message from worker ${JSON.stringify(data)}`); worker.terminate(); }; worker.onerror = function (error) { console.log(error.filename, error.lineno, error.message); }; </script> </body> </html> Copy the code
“task.js“
The code executed by the Dedicated Worker is as follows:
onmessage = function (message) {
let data = message.data;
console.log(`Worker: Message from main thread The ${JSON.stringify(data)}`);
data.msg = "Hi from task.js";
postMessage(data);
}; Copy the code
After the above code runs successfully, the console prints the following:
Worker: Message from main thread {"id": 666,"msg": "Hello Semlinker"}
worker-demo.html:20 Main: Message from worker {"id":666, "msg":"Hi from task.js"}
Copy the code
In order to better understand the workflow of Web Worker, let’s take a look at the process of WebKit loading and executing Worker threads:
(photo: http://www.alloyteam.com/2015/11/deep-in-web-worker/)
5.3 Observing Dedicated workers
Seeing this, I believe some friends will be curious. After introducing so much knowledge about Web workers, where can you intuitively feel Web workers? Next, we will observe it from the following two perspectives.
5.3.1 Developer tools
Use Chrome as an example. First open Chrome Developer Tools and choose Sources -> Page:
5.3.2 Chrome Task Manager & Activity Monitor
After opening Chrome Task Manager, we can find the process ID corresponding to the current Tab page, which is “5194”. Then we can open the Activity monitor in macOS, select “5194”, and sample the process:
After sampling, we can see the complete thread information for the current render process, and the Dedicated Worker is highlighted in red.
Originally, he wanted to write “Web Workers you don’t know” in one go, but considering the feelings of some of his friends, he decided to split it into two parts to avoid the situation mentioned by the following group members.
In the next part, I will introduce “some common usage scenarios of Web Workers and related implementation of Deno Web Workers”. If you are interested, please remember to pay attention to Bob.
Read other “What You Didn’t Know about XXX Series tutorials”
- Blob you don’t know
- You don’t know WeakMap
Vi. Reference resources
- w3.org – workers
- MDN – Web_Workers_API
- web-workers-vs-service-workers-vs-worklets
- introduction-to-web-worker
- web-workers-demystified
- Deep understanding of Web workers
- From browser multi process to JS single thread, JS running mechanism is the most comprehensive combing
This article is formatted using MDNICE