Workers and shared workers

    All popular languages ​​have threads. In browser-based javascript, workers are used for parallel processing.
    Under the cat is a story about how to use them, what limitations there are in the workers and about the features of interacting with them in different browsers.


    What is worker


    A separate context for performing background tasks that does not block the UI. Usually, the worker is created as a separate script, the resources of the worker live in the process of creating the page. Shared worker is the same, but can be used from multiple pages.
    The worker has:
    • navigator
    • location
    • applicationCache
    • XHR, websocket
    • importScripts for synchronous script loading


    Creating a Worker


    Worker is created from a separate script:
    var worker = new Worker(scriptUrl);
    var sharedWorker = new SharedWorker(scriptUrl);
    

    Shared worker is identified by URL. To create a second worker from a single file, you can add some 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));
    


    Creating a worker from a worker is possible only in Firefox. In Chrome, you can create a shared worker from the page and transfer its port to another worker (see below).

    Worker Limitations


    Dom


    In the worker, you cannot use the DOM; instead of 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 facilities


    You cannot return an object from workers. There are no locks or other thread safety features in javascript, so you cannot transfer objects by reference from workers, everything sent to or from worker will be copied.

    CORS


    So far, workers do not support CORS, you can only create a worker by downloading it from your domain.

    Stack size


    For workers, a smaller stack size is allocated, sometimes it matters:
    Chrome / osxFirefox / osxSafari / osxChrome / winFirefox / winIE11 / win
    web20 80048,00063,00041,90051,00063,000
    worker5,30043,3006 10021 30037,00030 100

    console


    Until recently, it was not, but usually it already is. In some browsers there is no console in the workers, so before accessing it is better to check its availability.

    Worker interaction


    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 like this:
    // worker
    self.onmessage = function(e) { e.data... };
    // shared worker
    self.onconnect = function(e) {
        var port = e.ports[0];
        port.onmessage = function(e) { e.data... };
    };
    


    Similarly, and vice versa, from the worker, you can call either self.postMessage, or port.postMessage for shared workers.

    The postMessage method uses the structured clone algorithm to clone objects. This is not the same as serialization in JSON. The algorithm can:
    • copy RegExp, Blob, File, ImageData
    • restore circular references

    But can not:
    • Error, Function, DOM elements (error will fall)
    • properties and prototypes (they are not inclined)


    Transferables


    You can transfer something by reference. To do this, there is a second parameter in postMessage, transferList :
    var ab = new ArrayBuffer(size);
    worker.postMessage({ data: ab }, [ab]);
    

    In transferList, you can pass a list of objects to be moved. Only ArrayBuffer and MessagePort are supported. In the calling context, the object will be neutered: ArrayBuffer will have zero length, and attempting to resubmit it 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 (the 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 send messages from one to another through the page code. But this is inconvenient, because: 1. you need to write additional code, 2. 2 times increases the number of interactions and data copying, 3. requires code execution in a UI context.
    Worker can be taught to communicate with the shared worker, passing the shared worker port to it, and we lose the port being transferred in the UI context; if you need it, you will need to reconnect to the shared worker, creating it again. The port transfer looks like this:
    worker.postMessage({ port: sharedWorker.port }, [sharedWorker.port]);
    // в worker-е поймать этот порт и сделать что-то с ним
    

    True, for synchronization, the V8 engine still uses the UI context, which can be seen by hanging the page for a while: the workers continue to work, and postMessage do not go between them, waiting for the UI context to become special.

    PostMessage performance


    The performance is different for several cases, different data sizes and the use of transferList (trlist):
    • dedicated worker
    • shared worker in the created process
    • shared worker in another process

    The table shows the number of data transfer cycles from worker and back per second.
    Chrome / osxFf / osxSafari / osxChrome / winFf / winIE11 / win
    dedicated: 10B9 3008,40021,0006,8007 3003,200
    dedicated: 10kB4,0007,0005,0003,0005,0001 800
    dedicated: 1MB805009060400200
    dedicated: 10MB8407752thirty
    dedicated: trlist: 10MB8,4001,1002,5006,2001 9002,200
    shared: 10B3 1008 300-2,2005 500-
    shared: 10kB1 8006 900-1,4004,500-
    shared: 1MB40500-32400-
    shared: 10MB440-453-
    shared: trlist: 10MB-260--1 800-
    shared-ipc: 10B3,000--2,700--
    shared-ipc: 10kB1,600--1,700--
    shared-ipc: 1MB40--thirty--
    shared-ipc: 10MB4--3--

    Conclusions that can be drawn from the data:
    • interaction costs with dedicated worker in chrome are less than with shared;
    • large amounts of data are much faster to transfer through transferList;
    • but still, transferList is not equivalent to sending a link or a few bytes.


    Killing a worker


    Decicated worker can be killed by calling worker.terminate (). This is not possible with shared worker, its execution will be stopped:
    • when it closes itself by calling self.close ()
    • when all the pages using it are closed (while the worker will not have the opportunity to finish the calculations)
    • when the user forcibly terminates it (for example, in chrome from chrome: // inspect)
    • when he falls, or the process of the page where he lives

    Let's try to call the process cache from the shared worker. Together with the worker, of course, the tab that created him 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 a worker or a page that uses it.

    Resource accounting in shared workers in Chrome


    SharedWorker lives the process in the page that created it. It takes into account and displays in the task manager the CPU and memory that the worker consumes. If the page is closed, its process with the worker will give up the memory used by the page (not immediately, some time after closing) and will remain alive while other pages use this worker. It is interesting that in this case such a process will completely disappear from the chrome statistics: neither the memory nor the CPU will be able to track the user in its internal task manager. This is unpleasant, because the user will probably not guess why the browser began to consume so many resources.

    Debugging Workers


    In chrome shared workers are available on the chrome page: // inspect / # workers:

    This is where console output from worker is written.
    Dedicated worker in chrome and IE is debugged in the page on which it is executed:

    In other browsers with debugging of workers, so far it’s bad.

    Can I Use ...


    Support for different workers on Can I Use . Briefly, in relation to today's web: worker is on modern browsers, sharedworker is 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.

    References


    Using Web Workers (MDN)
    The Basics of Web Workers
    Living Standard: Web workers
    Transferable Objects
    How fast are web workers?

    Also popular now: