All popular languages have threads. In browser javascript, workers are used for parallel processing.
Under the cut there is a story about how to use them, what limitations exist in the workers and about the peculiarities of interaction with them in different browsers.
What is a worker
A separate context for performing background tasks that does not block the UI. Usually a worker is created as a separate script, the worker’s resources live in the process of the page that created it. The shared worker is the same, but can be used from several pages.
In worker there are:
- navigator
- location
- applicationCache
- XHR, websocket
- importScripts for synchronous script download
Creating a worker
Worker is created from a separate script:
var worker = new Worker(scriptUrl); var sharedWorker = new SharedWorker(scriptUrl);
The shared worker is identified by a URL. To create a second worker from one file, you can add any parameter to the URL (worker.js? Num = 2).
')
Worker can be created without a separate file. For example, create it from the function text:
var code = workerFn.toString(); code = code.substring(code.indexOf("{")+1, code.lastIndexOf("}")); var blob = new Blob([code], {type: 'application/javascript'}); worker = new Worker(URL.createObjectURL(blob));
You can create a worker from a worker only in Firefox. In Chrome, you can create a shared worker from a page and transfer its port to another worker (see below).
Constraints of workers
Dom
In a worker, you cannot use DOM, instead of a window, the global object is called self. Cannot access localStorage and draw on canvas. The same limitations are usually found in all desktop APIs: access to windows only from the UI thread.
Access to objects
From worker it is impossible to return object. There are no lock-s and other thread-safety features in javascript, so objects cannot be transferred from a worker by reference, everything sent to or will be copied from the worker.
CORS
So far, workers do not support CORS, you can create a worker only by downloading it from your domain.
Stack size
For workers, a smaller stack size is allocated, sometimes it matters:
| Chrome / osx | Firefox / osx | Safari / osx | Chrome / win | Firefox / win | IE11 / win |
---|
web | 20,800 | 48,000 | 63,000 | 41 900 | 51 000 | 63,000 |
worker | 5 300 | 43 300 | 6,100 | 21 300 | 37,000 | 30 100 |
console
Until recently, it was not, but usually now there is. In some browsers, there is no console in the worker, so it’s better to check its availability before accessing it.
Interaction with worker
After creating a worker, you can send him a message:
worker.postMessage({hello: 'world'}); worker.onmessage = function(e) { e.data ... }; sharedWorker.port.postMessage({hello: 'world'}); sharedWorker.port.onmessage = function(e) { e.data... };
Subscribe to a message in worker as follows:
Similarly, back from a worker, you can call either self.postMessage or port.postMessage for shared workers.
The
postMessage method uses the
structured clone algorithm for cloning objects. This is not the same as serialization in JSON. The algorithm is able:
- copy RegExp, Blob, File, ImageData
- restore circular references
But can not:
- Error, Function, DOM elements (the error will fall)
- properties and prototypes (they are not cloned)
Transferables
You can send something by reference. For this, there is a second parameter in postMessage,
transferList :
var ab = new ArrayBuffer(size); worker.postMessage({ data: ab }, [ab]);
In transferList you can transfer a list of objects that will be moved. Only ArrayBuffer and MessagePort are supported. In the calling context, the object will be cleared (neutered): the ArrayBuffer will have a zero length, and trying to send it again will result in an error:
Uncaught DOMException: Failed to execute 'postMessage' on 'Worker': An ArrayBuffer is neutered and could not be cloned.
The interaction of two workers
In Firefox, you can create a worker from a worker (standard defines
subworkers ).
Now in chrome, you cannot create a worker from a worker, and sometimes workers need to interact with each other. The easiest way is to make the transfer of messages from one to another through the page code. But this is inconvenient because: 1. it is necessary to write additional code, 2. increases the number of interactions and data copying by 2 times, 3. requires execution of the code in a UI context.
The Worker can be taught to communicate with a shared worker by passing him the port of a shared worker, while losing the transmitted port in a UI context; if it is needed, it will be necessary to reconnect to the shared worker, creating it again. The transfer port looks like this:
worker.postMessage({ port: sharedWorker.port }, [sharedWorker.port]);
True, the V8 engine uses the UI context for synchronization, as can be seen by hanging the page for a while: the workers continue to work, and the postMessage do not go between them, waiting for the UI context to be specific.
PostMessage performance
Performance is different for several cases, different data sizes and use of transferList (trlist):
- dedicated worker
- shared worker in creating process
- shared worker in another process
The table shows the number of data transfer cycles from the worker and back per second.
| Chrome / osx | FF / osx | Safari / osx | Chrome / win | FF / win | IE11 / win |
---|
dedicated: 10B | 9 300 | 8,400 | 21,000 | 6,800 | 7 300 | 3,200 |
dedicated: 10kB | 4,000 | 7,000 | 5,000 | 3,000 | 5,000 | 1,800 |
dedicated: 1MB | 80 | 500 | 90 | 60 | 400 | 200 |
dedicated: 10MB | eight | 40 | 7 | 7 | 52 | thirty |
dedicated: trlist: 10MB | 8,400 | 1,100 | 2,500 | 6,200 | 1,900 | 2,200 |
shared: 10B | 3,100 | 8 300 | - | 2,200 | 5 500 | - |
shared: 10kB | 1,800 | 6,900 | - | 1,400 | 4,500 | - |
shared: 1MB | 40 | 500 | - | 32 | 400 | - |
shared: 10MB | four | 40 | - | four | 53 | - |
shared: trlist: 10MB | - | 260 | - | - | 1,800 | - |
shared-ipc: 10B | 3,000 | - | - | 2 700 | - | - |
shared-ipc: 10kB | 1,600 | - | - | 1,700 | - | - |
shared-ipc: 1MB | 40 | - | - | thirty | - | - |
shared-ipc: 10MB | four | - | - | 3 | - | - |
Conclusions that can be drawn from the data:
- the cost of interaction with a dedicated worker in chrome is less than with a shared one;
- large amounts of data transfer much faster through transferList;
- but still transferList transfer is not equivalent to sending a link or several bytes.
Killing a worker
A decicated worker can be killed by calling worker.terminate (). It’s not possible with shared worker, its execution will be terminated:
- when it closes itself by calling self.close ()
- when all the pages that close it are closed (the worker will not be able to complete the calculations)
- when the user forcibly terminates it (for example, in chrome from chrome: // inspect)
- when either he falls or the process of the page where he lives
Let's try to call the process crash from a shared worker. Together with the worker, of course, the tab that created it will also fall. In the tab where it was still used, we will see the following message:

Unfortunately, now there is no regular way to track the closure of the worker or the page that uses it.
Resource accounting in shared worker in Chrome
SharedWorker lives by the process in the page that created it. It takes into account and displays in the task manager CPU and the memory that the worker consumes. If the page is closed, its worker process will return the memory used by the page (not immediately, some time after closing) and will remain alive while other pages use this worker. Interestingly, this process will completely disappear from chrome statistics: neither the memory nor the CPU will be able to be tracked by the user in his internal task manager. This is unpleasant, because the user is most likely not to guess why the browser began to consume so many resources.
Debugging workers
In chrome shared, workers are available on the chrome: // inspect / # workers page:

This is where the console output from the worker is written.
The dedicated worker in chrome and IE is debugged in the page where it is executed:

In other browsers with debugging, workers are bad for now.
Can I Use ...
Support for different workers on
Can I Use . Briefly, with reference to today's web: the worker is on modern browsers, sharedworker on advanced desktop browsers, serviceworker is too early.
...
Everything written is relevant for the summer of 2015, do not forget that the web is changing rapidly.
Links
Using Web Workers (MDN)The Basics of Web WorkersLiving Standard: Web workersTransferable objectsHow fast are web workers?