“This is the third day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021.”
For design reasons, JS in browsers has long been single-threaded, with an EventLoop driving an asynchronous event to do the work. However, as the front-end page becomes more and more complex, some applications inevitably need to perform a large number of calculations in the front-end, which will occupy the main thread for a long period of time, and users will feel obvious page lag. In this scenario we can use WebWorker to solve the problem.
Using WebWorker
New WebWorker creates a WebWorker in the browser:
// main.js
const worker = new Worker("./worker.js");
Copy the code
The code in worker.js runs in the worker thread, and the postMessage is used to pass messages between the worker thread and the main thread:
// main.js
worker.onmessage = (e) => {
console.log(e.data);
};
worker.postMessage('hello worker');
Copy the code
// worker.js
onmessage = (e) => {
console.log(e.data);
postMessage("hi from worker");
};
Copy the code
The global context of worker thread is not window but self, so the above program is exactly the same as the following:
// worker.js
self.onmessage = (e) => {
console.log(e.data);
self.postMessage("hi from worker");
};
Copy the code
Since Windows cannot be accessed, the APIS on Windows cannot be accessed either. However, many apis have corresponding implementations in worker. You can check this link. What needs to be focused here is that DOM-related apis cannot be used in worker, which is related to the design concept of worker. Worker is used to do calculation work to release the pressure of the main thread so as to improve user interaction experience. Basic user interaction should still be completed by the main thread.
Another issue that needs to be noted is that the data between the worker and the main thread is passed by replication. See the following example:
// main.js
const worker = new Worker('./worker.js')'
const data = { name: "tom", age: 10 };
worker.postMessage(data);
worker.onmessage = () => {
console.log('data in main', data);
}
// worker.js
onmessage = (e) => {
e.data.name = 'jerry';
console.log('data in worker', e.data);
postMessage('changed');
}
// output: data in worker { name: "jerry", age: 10 }
// data in main { name: "tom", age: 10 }
Copy the code
The worker thread does not modify the main thread data. The advantage of replication lies in the data independence between threads. Therefore, although worker is multi-threaded, the data of the main thread will not be modified by other threads, and the data competition problem of traditional multi-threaded programming will not occur. However, the disadvantages of replication are also obvious. The replication process will undergo data serialization and deserialization, which will bring more extra overhead when the data volume is large.
Transferable object
Browsers have Transferable objects that transfer ownership, and when the data is transferred, the original reference is not available, much like the mechanism in Rust. The following types of portable objects are supported in browsers:
- ArrayBuffer
- MessagePort
- ReadableStream
- WritableStream
- TransformStream
- AudioData
- ImageBitmap
- VideoFrame
- OffscreenCanvas
In WebWorker, the postMessage method supports the second parameter, which controls whether the transferable object is copied or transferred if there is a transferable object in the passed data, as shown in the following example:
/ / = = = = = = = = = do not use the transfer = = = = = = = = = / / the main js const worker = new worker (". / worker. Js "); Uint8Array([2, 4, 6, 32, 6, 34,5]); worker.postMessage(data); worker.onmessage = (e) => { console.log('data in main', data); }; // worker.js onmessage = (e) => { e.data[0] = 200; console.log('data in worker', e.data); postMessage('changed'); } // output: data in worker Uint8Array(7) [200, 4, 6, 32, 6, 34, 5, buffer: ArrayBuffer(7), byteLength: 7, byteOffset: 0, length: 7] // data in main Uint8Array(7) [2, 4, 6, 32, 6, 34, 5, buffer: ArrayBuffer(7), byteLength: 7, byteOffset: 0, length: 7] / / = = = = = = = = = use transfer = = = = = = = = = / / the main js const worker = new worker (". / worker. Js "); Uint8Array([2, 4, 6, 32, 6, 34,5]); worker.postMessage(data, [data.buffer]); worker.onmessage = (e) => { console.log('data in main', data); }; // worker.js onmessage = (e) => { e.data[0] = 200; console.log('data in worker', e.data); postMessage('changed'); } // output: data in worker Uint8Array(7) [200, 4, 6, 32, 6, 34, 5, buffer: ArrayBuffer(7), byteLength: 7, byteOffset: 0, length: 7] // data in main Uint8Array [buffer: ArrayBuffer(0), byteLength: 0, byteOffset: 0, length: 0]Copy the code
When no transfer is used, the data in the incoming Worker is copied, and the main thread and Worker have a copy of each other’s data, which do not affect each other. When you add the second parameter [data.buffer] to postMessage, the data becomes a shifting behavior, and the buffer in the original Uint8Array is no longer there, instead printing an empty buffer.
SharedArrayBuffer
In addition to using transferable objects, sometimes you really need to share objects between two threads. In this case, you can use SharedArrayBuffer, which creates a block of memory that can be shared. As with other programming languages, multithreaded access to shared data presents data race problems, so the browser also has an Atomics object that provides atomic manipulation capabilities. SharedArrayBuffer and Atomics complement WebWorker’s weaknesses and enable full multithreaded programming capabilities in the browser. The cost of this solution is heavy, so it is not used as a typical front-end project selection solution. It can be used when higher performance is required in some specific scenarios. SharedArrayBuffer and Atomics will not be covered in this article, but there will be an opportunity to write a special article later.
Other types of workers
In addition to webworkers, there are several other things in browsers that are also called workers or similar to workers.
SharedWorker
Like a name, SharedWorker can be shared to multiple pages. Like other workers, SharedWorker also runs in an independent thread and can communicate with multiple pages using SharedWorker (subject to the same origin policy).
// main.js
const worker = new SharedWorker("./worker.js");
worker.port.start();
worker.port.onmessage = (e) => {
console.log("from worker", e.data);
};
worker.port.postMessage("to worker");
// worker.js
onconnect = function (e) {
const port = e.ports[0];
port.onmessage = function (e) {
console.log(e.data);
port.postMessage("success");
};
};
Copy the code
Here you need to use port to interact with worker. The SharedWorker created from multiple pages is the same. You can debug SharedWorker in Chrome ://inspect/#workers.
ServiceWorker
The ServiceWorker is a proxy server that intercepts network requests from Web applications and processes them based on network conditions, providing offline user experience for Web applications. ServiceWorker is the core technology for implementing PWA, and is not covered in this article, except that it also runs in a separate thread.
Worklet
The Worklet is a lightweight worker that gives users access to the underlying rendering pipeline. There are currently four types of worklets:
- PaintWorklet
- AudioWorklet
- AnimationWorklet
- LayoutWorklet
Worklet is only used in specific scenarios, so you can read the documentation if you are interested.
reference
Developer.mozilla.org/en-US/docs/…