10 ms·
When to use web workers
- simonw 7y agoWeb Workers were added to Firefox ten years ago this month, so they're definitely not new technology. I think this is a great call to action to actually start taking advantage of them.
- deleted 7y ago[deleted]
- stefan_ 7y agoPeople have been taking plenty advantage, they are an indispensable tool to turn all of these CPU bugs or DRAM attacks into working browser exploits. Also those bitcoin miners of course. Maybe if no one else is taking advantage it is time to simply throw them out.
- Matheus28 7y ago“I don’t use it, therefore no one else uses it” As an example, emscripten uses WebWorkers and SharedArrayBuffers to implement multithreading (unfortunately or not, Chrome is the only browser that supports SharedArrayBuffer, others have disabled it).
- stefan_ 7y agoI suggest you look up why they disabled it. And that is still not an application of WebWorkers! It would be much more helpful to the cause if you could point to some actually used app out there principally relying on WebWorkers. Until then, it's been a lot of pain, zero gain.
- Jack000 7y agoa bit niche, but here's one of my projects - svgnest.com in addition to the render blocking issue brought up by this article, there are a lot of applications for cpu-bound web apps (eg. anything requiring machine learning or computer vision), but in my experience web workers are not very reliable for this purpose.
- infinite8s 7y agoI use webworkers to run an in-memory streaming pivoting engine (compiled from C++ to wasm) to build real-time Tableau-like dashboards that run fully in the browser (which runs fairly well for 10M row datasets or less). Network IO and the compute is handled in the webworker, which allows the main UI thread to remain responsive.
- jacobush 7y agoThis whole thread was quite overwhelming. I was starting to feel, can we just have a normal CPU machine and some C in it? That is horrible too, but in a way I can understand. And sure enough, emscripten gets mentioned. :-D
- tree123 7y agoare you updating diep.io? it is showing lastModified 17th June 2019
- laughinghan 7y agoThey're pretty important for this free online graphing calculator (widely used by high school students and teachers and even starting to be used on some standardized tests): http://desmos.com/c http://desmos.com/c There is also some niche use as a component of sandboxing: https://gist.github.com/pfrazee/8949363 https://gist.github.com/pfrazee/8949363 Your point is well-made though. Very few kinds of webapps are CPU-bound, graphing calculators being notable as an exception (and even then, Desmos made do with the main thread before Web Workers were usable). And while lots of webapps could use plugins, browsers could provide an API for sandboxing code that runs in the main thread, or with a GIL. The reality is, though, the cat is out of the bag. WHATWG is committed to making the Web into a fully-featured platform for WORA apps with all the same capabilities as a desktop app, and multithreading is one of those capabilities. Even if the next Spectre/Meltdown dooms Web Workers in their current incarnation, browsers will run workers in Docker containers if they have to.
- hinkley 7y agoReading this thread I was thinking this seems like it's turning into XHR all over again. It was a long time from XHR to 'ajax'
- taf2 7y agoI wish SharedWorkers were not removed from Safari. They are super useful for sharing a single websocket connection between multiple open tabs. Something you can’t do with ServiceWorkers
- dassurma 7y agoAgreed. I personally find BroadcastChannel more flexible, but they are not in Safari either :(
- streptomycin 7y agoYeah shared worker are awesome. I love having a single instance of the core of my app running in a worker, and then each tab the user opens is just a UI view of that one instance. It greatly simplifies keeping things in sync across tabs. But it's just not possible for Safari... you either have to make your code much more complicated, or add some hack to prevent users from opening your app in multiple tabs at the same time :(
- echelon 7y agoWhat percentage of your users use Safari? If it isn't impactful to your business, you could ask them to use Firefox or Chrome. I realize we need more browser diversity and not less, but at the same time we should encourage all participants to keep up so that the burden doesn't fall on smaller players.
- zhte415 7y agoWhat is the use-case? Why is static not better? What are the edges?
- dassurma 7y agoStatic _is_ better for a good bunch of performance metrics. But what about interactive content like https://proxx.app https://proxx.app? The article is mostly aimed at use-cases where some sort of logic in JavaScript is required.
- whereareyouwow 7y agoYes @dassurma. But web workers are not everything. Your own blog post does not score 100 : https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fdassur.ma%2Fthings%2Fwhen-workers%2F&tab=desktop https://developers.google.com/speed/pagespeed/insights/?url=... And those issues are not necessarily addressed with web workers. Good basic static design matters too.
- dassurma 7y agoI never claimed that Web Workers are the silver bullet that will absolve us from all our problems. On the contrary, I don’t think such a silver bullet will ever exist. I am saying that we should be using web workers to keep the main thread free. That is completely orthogonal to good static design, proper bundling, right caching headers, code splitting, asset hashing etc etc.
- whereareyouwow 7y agoI agree with you on keeping the main thread free. What are your thoughts on WASM via workers?
- dassurma 7y agoWe are using loads of Wasm in Workers in https://squoosh.app https://squoosh.app Since Wasm is synchronous, I’d say it should almost always be run in workers except if the module needs access to some main-thread-only API.
- EdSharkey 7y agoI don't like how the browser has to structured clone objects sent between threads. Seems like on low end devices, that could tie up tons of ram and have out of memory failure modes not seen on beefier phones. Reading the article, the author seems to advocate sending js events over from Dom thread to worker, doing all event processing on the worker, and sending commands from the worker back to the Dom that get processed into Dom mutations. It strikes me that OP's approach will offload the bulk of the CPU time spent doing clones onto workers, so at least we got that. Short of the experimental OffscreenCanvas API[1], I don't see any way to avoid the structured clone memory tie-ups when communicating from worker to dom. Are there any other patterns or approaches that can help limit the memory consumption when employing workers? [1] https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCa...
- kabwj 7y agoIf you really care about the performance of your site on the mobiles that poor people use then render server-side as much as you can.
- kinlan 7y agoYeah - that's been pretty much our guidance (I'm on Surma's team) - but we know you can build interactive experiences on the client on these low end devices too, but it has to be off main thread.... In the end, it's a blend .
- skybrian 7y agoBut they may have a bad network connection as well.
- hinkley 7y agoCorrect me if I'm wrong, but isn't one of the uses of web workers to do resource management via local storage, so that multiple tabs can share one set of resources? Not everybody is using web workers for that, but that could address both issues to a degree.
- chrismorgan 7y agoI’ve been experimenting with all kinds of things and ideas for making a Rust-powered web frontend system that is designed to be completely functional with server-side rendering, with scripting on the frontend being deliberately optional, bringing things back closer to the old-style non-isomorphic server-side rendering, doing things like wrapping all the buttons in forms so that they will, in the absence of local scripting to intercept it and do it on the client side in a potentially more efficient way or with transitions or such, leave it to the server to render the result as it would have been after that button was clicked. In conjunction with this, I’ve long been thinking about a new style of isomorphic rendering: where instead of running on the server and in the DOM, you run the server code on the server and in a service worker on the client side. (Aside: Svelte has been a major inspiration in the last few months; learn from it that SSR doesn’t need to mean VDOM: if you’re a compiler you can take different approaches.) Cloudflare Workers, when it came along, brought clarity to what I had been thinking at about that time, that it might work to have a pure API backend, and an HTML renderer that speaks to that API and can run either on the server or locally in a service worker on the client: truly use the same interface for both. (To clarify: this approach does not preclude additional client-side scripting for interactivity; but it would lend itself to a light hand on client-side interactivity.) Now to the relevant point here: a couple of weeks ago I started toying with the idea of doing just about all of the client-side scripting (I mean the stuff for interactivity without full page loads) in workers, leaving the work done on the UI thread being purely applying UI changes that were even calculated in a worker, and event dispatch. This might be able to be slotted into the service worker in some way, or it might need to be another worker. The essence of what I have in mind is that rendering in the worker would, instead of applying changes to the DOM, emit a byte code (I’ve been looking a very little into Glimmer.js’s), which can then be fairly efficiently passed back to the UI thread through one ArrayBuffer or SharedArrayBuffer, to be applied by a small unit of code (probably JS rather than Rust) to the DOM. In the last few days I’ve been playing with events, and adding the listeners on the document root and doing dispatch manually, through components more than through elements, in a way that lets the framework deal with hierarchical ownership (very Rusty, allowing you to skip GC/RC types) rather than something closer to the ECS style (what is mostly done for UI things in Rust), and I think the approach has promise. (Apart from the hierarchical ownership aspect, this is basically what our framework Overture that we use for FastMail does—and I should clarify at this point that these experiments of mine are personal and nothing whatsoever to do with FastMail, where we have no workers at all, like almost all sites—though I wrote one this very day that might be deployed in the coming week). Events would be serialised on the UI thread and passed through to the worker. All events would thus need to be passive (i.e. no preventDefault()); though there will doubtless need to be some alternative channel for events that need to preventDefault, most notably clicking on links that should route instead, and form submit; I’m not sure how that will work. Some parts of this I’ve written code for, to experiment with ideas, but especially the parts involved with workers have almost entirely been thought experiments. I’ve been thinking about the approaches SwiftUI takes too. Lots of interesting stuff to learn from it as well. I’ve written all of this purely for thinking about. Maybe others will find it interesting. (I shan’t be able to respond to anyone that replies for the best part of a day.)
- vijaybritto 7y agoIf there was a vdom library that moved the diffing part to a web worker, would you have used it? or is it still costly to do any diffing at all?
- dassurma 7y agoIt's still costly, but at least it'd not be blocking the main thread. I'd definitely give it a try!
- vijaybritto 7y agoDid you ever consider using svelte? Or was it not around when you developed this site?
- dassurma 7y agoI have played with Svelte, not used it for any project yet. When you say, “this site”, do you mean my blog? Because that one’s fully static using 11ty.
- vijaybritto 7y agoSorry I meant proxx.app
- dassurma 7y agoAh, no Svelte was around, but we were on a pretty tight deadline and had a good amount of experience with Preact in the team. We didn’t feel comfortable increasing the risk by using an unknown framework.
- vijaybritto 7y agoGot it. Thanks!
- whereareyouwow 7y agoHas anyone used web workers at the edge ... like with cloudflare workers?
- floatboth 7y agoDespite the similar naming, "Web Workers" is a specific browser API that has nothing to do with "the edge" or any server-side thing.
- whereareyouwow 7y agoPoint is well taken. I have not tried cloudflare workers yet. It seemed like at least the architecture was the same. But I guess you would not have access to the full browser API. It seems like you can still play around with request / response side of things though.
- kentonv 7y agoCloudflare Workers is so-named because it uses the same API as Service Workers, which are one kind of Web Worker. That said, Cloudflare Workers currently implements only a subset of the APIs that are normally available in the browser, though missing APIs are being added all the time. So, your understanding is basically correct. (I'm the tech lead for Cloudflare Workers.)
- kbumsik 7y agoMy experience with Web Workers was terrible. It's not because of the Web Workers itself but there is no proper standard way to use modules inside of the worker. Because it requires a separated .js file to instantiate, bundlers like webpack tend to introduce weird and non-standard way to work with [1]. It is usually fine when you use it your own project since you are going to stick with a bundler you choose anyway, but the worst things happens when you try to publish a JS library that uses a web worker internally. You cannot just publish the source because bundlers won't recognize require() and imports syntax in the web worker source code out of the box. There is a ES standard: new Worker("worker.js", { type: "module" }); but no browsers implementations yet and some people aren't happy with this because it is not async [2]. [1]: https://github.com/webpack-contrib/worker-loader/blob/master/README.md#config https://github.com/webpack-contrib/worker-loader/blob/master... [2]: https://bugs.chromium.org/p/chromium/issues/detail?id=680046 https://bugs.chromium.org/p/chromium/issues/detail?id=680046
- dassurma 7y agoAgreed. I am quite annoyed that no browser has shipped modules in workers. There is little incentive as Workers are sorely underused. Since I mostly use rollup, I ended up writing a plugin[1] that allows you to pretend that modules are in workers. It compiles down to AMD under the hood and uses a very minimalistic worker. [1]: https://github.com/surma/rollup-plugin-loadz0r https://github.com/surma/rollup-plugin-loadz0r
- bufferoverflow 7y agoCan someone explain why the ES standard doesn't have something like this: new Worker(function(params) { ... compute heavy stuff here ... });
- dassurma 7y agoThe problem is mostly in that all these functions are closures, and scope can’t easily be transferred. Domenic and I have been working on a proposal[1] called Blöcks to introduce transferable functions to JavaScript. In the meantime, I wrote Clooney[2] ontop of Comlink[3] that gives you almost that. [1]: https://github.com/domenic/proposal-blocks https://github.com/domenic/proposal-blocks [2]: https://github.com/GoogleChromeLabs/clooney https://github.com/GoogleChromeLabs/clooney [3]: https://github.com/GoogleChromeLabs/comlink https://github.com/GoogleChromeLabs/comlink
- msoad 7y agoAs a UI developer i want to move scrolling and style updates out of main thread. Every WebWorker tutorial is taking about how a “computationally heavy task” can go out of main thread. But rarely anyone does computation in the client. I would say Animation Worklets are the workers that will make an impact on delivering multithreaded UIs in web. https://www.chromestatus.com/feature/5762982487261184 https://www.chromestatus.com/feature/5762982487261184
- dassurma 7y agoYes, that’s exactly what AnimationWorklet is for. That being said, I think even smaller-scale things like state management and hitting APIs should be moved to workers. It all increases resilience and buys headroom for low-end phones.
- prasanthv 7y agoAgreed, I’ve always wondered why this isn’t done more often, it seems like the perfect place for tasks such as those. That being said, I’ve been a web developer for over 5 years and have never actually decided to move tasks to web workers; it’s about time I change that. I also find it weird when devs complain about the lack of shared state and how it’s some how a limitation. Messaging passing architectures haven proven key for modelling concurrency in many powerful languages and frameworks like Erlang/Elixir and Go channels to name a few. Anyways thanks for writing this up. Proper breakdown and grouping will have unseen benefits for sure, and in this case, very seen :)
- eddiezane 7y agoIf you haven't seen this before it might interest you. Nolan worked on offloading most of the work to threads to achieve 60 fps on mobile. http://www.pocketjavascript.com/blog/2015/11/23/introducing-pokedex-org http://www.pocketjavascript.com/blog/2015/11/23/introducing-...
- chii 7y agoThat's pretty cool!
- jhayward 7y agoThank goodness for Safari's reader mode. This article was unreadable without it. Why do people do this?
- hashberry 7y agoCan you elaborate? I enjoyed the layout, colors, fonts etc, but curious on why the page is not acceptable.
- jwr 7y agoI have a large ClojureScript app (https://partsbox.io/ https://partsbox.io/) and I really, really wanted to use WebWorkers. I wanted to run larger tasks like indexing for search, or pricing calculations in WebWorkers. But it seems to me that Webworkers were designed with an extremely narrow use case in mind. The restrictions placed on them make them essentially useless for me. To do anything ever remotely useful, I would need to duplicate my entire database in web workers, and then communicate with them through a thin straw. Database updates would need to be performed both in the main app database and in web worker threads. The way they are restricted, I just can't figure out how to make any meaningful use of them.
- dassurma 7y agoIndexedDB is shared between workers and main thread, in case that helps.
- jwr 7y agoIndeed, and it is something I'm looking at. But at the moment my database is really native Clojure data structures, which has fantastic advantages. I would need to start storing them in a "traditional" database, introducing additional layers, serialization, overhead and bugs.
- krn 7y ago> I would need to start storing them in a "traditional" database, introducing additional layers, serialization, overhead and bugs. If the app state is stored in a single atom, I would use localForage[1] (available as a cljsjs package[2]) to keep it in sync between web workers and the main thread. It would also make the app accessible offline. [1] https://github.com/localForage/localForage https://github.com/localForage/localForage [2] https://clojars.org/cljsjs/localforage https://clojars.org/cljsjs/localforage
- impostervt 7y agoReally, really wish you could do canvas manipulation with web workers. There's some experimental browser stuff, but nothing standard. I'd like to be able to do image manipulation with web workers.
- dassurma 7y agoOffscreenCanvas is exactly that! It's standardized, but only chrome has implemented it so far, sadly.
- Ruphin 7y agoHey @dassurma, can you explain your task scheduling function? As far as I can tell it schedules a micro task, and there is no meaningful difference between that and other microtask scheduling systems: https://codepen.io/ruphin/pen/qzbgYr?editors=0012 https://codepen.io/ruphin/pen/qzbgYr?editors=0012
- dassurma 7y agoAll your logs are executed immediately. .then() takes a callback function!! FYI: AsyncTask and MicroTask are equivalent, and so are my task() function and your OnMessageTask
- Ruphin 7y agoAh, I messed up in my haste to experiment :) I expaned my tests a bit and your version does indeed queue a task and not a micro task. I did find it to be somewhat less reliable than setTimeout, is there a particular reason why you used this scheme instead of a setTimeout based solution to queue tasks? Why did you choose this method in particular to queue tasks? Also, AsyncTask and MicroTask are not fully equivalent. MicroTask is equivalent to a bare `await 0;`, but because AsyncTask wraps that in another async function it will queue a second micro task when the first one resolves, and resolve the promise when the second micro task resolves :)
- dassurma 7y agoWhy do you think it's less reliable? setTimeout gets clamped by the browser to a minimum of 4ms, so you waste a lot of time when all you want is a task boundary.
- Ruphin 7y agoI tried so many janky different ways that I don't quite remember how to reproduce it, but I found that setTimeout was more likely to always execute once per animationFrame, where using MessageChannel would sometimes execute more than once per frame. I guess the minimum timeout added to setTimeout makes it more likely to not hit twice between paints. The test setup I have now gets pretty clean results. One final question would be why you have this uid system with attaching and removing event listeners? I think MessageChannels are order preserving, so you can do with a single EventListener that consumes events off a queue, or am I missing something? ``` const { port1, port2 } = new MessageChannel(); const taskQueue = []; port2.start(); port2.addEventListener("message", () => { taskQueue.shift()(); }); const task = () => { return new Promise(resolve => { taskQueue.push(resolve); port1.postMessage(0); }); } ```
- ficklepickle 7y agoI really enjoyed the article! Thanks! You other posts look interesting and I have bookmarked your blog. A tiny typo in the second-to-last section: specture -> spectrum Thanks again!
- gridlockd 7y agoI think this is BS, practically all of the performance problems on the Web are related either to the DOM or CSS, which Web Workers can't fix. If you have a phone from 2014, something's gonna jank. It doesn't mean you're excluded. It means you'll have to live with jank. You'll get over it.
- chrismorgan 7y agoMany websites have too much of everything, but UI-thread JS is definitely the most common largest performance problem that prevents you from using pages. It’s the biggest thing where an architectural re-think has the biggest effect—though certainly just cutting down on all things will improve performance too.
- gridlockd 7y agoCan you show me an example? Like I said, I call BS on this. What website does a non-trivial amount of pure JS on the main thread? What even is the use-case? React reconciliation? Secondly, how does it prevent you from using a website? Sure, jank is annoying, but it doesn't entirely prevent you from using the site. Perhaps you give up on the site, but that's up to you.
- chrismorgan 7y ago> What website does a non-trivial amount of pure JS on the main thread? I challenge you to run a browser with no ad-blocker installed for one week, then block JavaScript (e.g. with NoScript; or with uMatrix and block scripts; or by setting javascript.enabled to false in Firefox). Your eyes will be opened at just how much JS is run and how much it slows things down. > What even is the use-case? Mostly analytics, ad-serving, privacy invasion and laziness/inefficient coding. > how does it prevent you from using a website? When it’s bad enough (which it often is, on slower mobile devices), you reach the point where you can’t perform tasks in a timely fashion. I chose the word “prevents” deliberately, because that is how it ends up.
- 7y ago
- troxwalt 7y agoNot related to the article, but the design and css styling of the blog is really interesting. Makes use of CSS variables as well as a couple properties that I've never seen before. (--mask, font-variation-settings)
- ljoshua 7y agoThe --mask is probably a CSS variable.
- vincentriemer 7y agoFWIW I believe that web worker adoption is more limited by tooling/knowledge than APIs. Web developers haven't thought about thread safety for decades (at least for those who have been around for that long) so it's more of a mental leap for web developers to think in terms of multiple threads. I had some promising (at the very least interesting) results with react-native-dom which runs all of React's reconciliation & your own business logic in a web worker: https://rndom-movie-demo.now.sh https://rndom-movie-demo.now.sh. I'll fully admit there's a lot more exploration/experimentation left to be done in this space though.
- acjohnson55 7y agoOne correction: I don't think thread safety is the issue at all. First of all, existing Javascript may not have parallelism in user code, but it does have concurrency. This means you absolutely have to deal with race conditions and resource sharing. Secondly, web workers can't access the mutable state in parallel, because they can only share data via message passing of copied data. This is what prevents thread safety from being a concern, while still enabling parallelism.
- pfrrp 7y agoFor those interested in moving apps (or parts) into a web worker, this project by the AMP team has some fascinating ideas (via DOM API replication in a worker): https://github.com/ampproject/worker-dom https://github.com/ampproject/worker-dom
- kybernetikos 7y agoLast time I used web workers (admittedly quite some time ago), they made everything slower. I encourage everyone who uses them to perform tests to ensure they're getting the benefit they think they are.
- samsonradu 7y agoOne good use case I had for WebWorkers was running heavy Regex on strings. Easy to decouple from the web app as I just had to pass the 2 strings to the Worker (text to scan and regex expression) and get back the result array. There is, however, a performance penalty there as the browser will ‘favor’ the main thread to for rendering and UX.
- ebj73 7y agoI don't think it's entirely true that desktop applications use separate threads for all tasks except UI related tasks. At least not for old win32 based applications. I think it's pretty common to do other small tasks too in the main thread, and to spawn new threads only when doing queues or other stuff that you know will perceptibly halt the running of the main user interface thread.