[HN Gopher] The missing standard library for multithreading in J...
___________________________________________________________________
The missing standard library for multithreading in JavaScript
Author : W4G1
Score : 128 points
Date : 2025-12-05 21:09 UTC (1 days ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| Zedriv wrote:
| This is cool! Hope we can get multi-threaded wasm some time soon.
| dwoldrich wrote:
| I haven't had a chance to use this library yet. Is there
| something about it that precludes calling wasm routines from
| the JavaScript closure passed to the web worker?
| wrs wrote:
| This seems very much worth a look!
|
| (I suspect, to paraphrase Greenspun's rule, any sufficiently
| complicated app using Web Workers contains an ad hoc, informally-
| specified, bug-ridden, slow implementation of half of this
| library...)
| bastawhiz wrote:
| I like this, but unfortunately it doesn't solve one annoying
| problem: lexical scope doesn't work and it will fail in an
| unexpected way.
|
| If you reference something lexically, your code fails at runtime.
| Want to use an import? You have to use import() inside the
| closure you pass to spawn(). Typescript doesn't know this. Your
| language server doesn't know this. Access a variable that shadows
| a built in global? Now you're accessing the built in global.
|
| The only way this could even be addressed is by having a full on
| parser. Even then you can't guarantee things will work.
|
| I think the only "fix" is for JS to introduce a new syntax to
| have a function that can't access lexical scope, returning a
| value that either extends a subclass of Function or has a cheeky
| symbol set on it. At least then, it'll fail at compile time.
| christophilus wrote:
| I'd love a way to be able to specify that sort of thing. I
| wrote a little server-side JSX rendering layer, and event
| handlers were serialized to strings, and so they had similar
| restrictions.
| kretaceous wrote:
| A linter rule provided by the library could be helpful here. I
| know it's just a workaround but probably easier than going for
| a solution that does compile time checks.
| austin-cheney wrote:
| This should be the expected behavior when multithreading. It is
| the expected behavior when executing a child process, such as
| node's child_process.fork.
| seniorsassycat wrote:
| Fork, and normal worker threads always enter a script,
| there's clearly no shared lexical scope. This spawn method
| executes a function, but that fn can't interact with the
| scope outside
| throwaway17_17 wrote:
| While I agree with GP that this should be the expected
| behavior, your comment raises what I think is a large
| problem/wild-goose-chase in 'modern' language designs
| implementing concurrency.
|
| The push from language designers (this applies across the
| high/low level spectrum and at all ranges of success for
| languages) to make concurrent code 'look just like'
| linearly read, synchronous, single-threaded code is
| pervasive and seems to avoid large pushback by users of the
| language. The complaints that should be made against this
| syntax design become complaints that code doesn't do what
| developers think it should.
|
| My position is that concurrent (and parallel) code IS NOT
| sequential code and languages should embrace those
| differences. The move to or design of async/await is often
| explicitly argued for from this position. But the semantic
| differences in concurrent code IMO should not be obscured
| or obfuscated by seeking to conform that code to sequential
| code's syntax.
| hombre_fatal wrote:
| As soon as I read your username, I had to read it out loud
| to my girlfriend. Why is it so funny
| buu700 wrote:
| I've been using a functionally identical implementation of this
| since I wrote it in my startup's codebase a decade ago. It's
| really handy, but definitely not without edge case issues. I've
| occasionally had to put in workarounds for false positive
| TypeScript/lint errors or a tool in the bundling pipeline
| trying to be too clever and breaking the output.
|
| Overall it's great, and I'm glad to see a generic
| implementation of it which will hopefully become a thriving
| open source project, but ultimately it's a kludge. What's
| really needed is for JS to introduce a native standardized
| version of this construct which TypeScript and the rest of the
| ecosystem have to play nice with.
| Etheryte wrote:
| There is a simple solution to this problem, but it's not very
| popular: do the same thing Workers do, require using a separate
| file. All the tooling works out of the box, you have no issues
| with lexical scoping, etc. The only downside is it's
| (currently) clunky to work with, but that can be fixed with
| better interfaces.
| pwdisswordfishy wrote:
| https://github.com/tc39/proposal-module-expressions is
| basically that fix.
| christophilus wrote:
| This looks great. If it works as well as the readme suggests,
| this'll let me reach for Bun in some of the scenarios where I
| currently reach for Go. Typescript has become my favorite
| language, but the lack of efficient multithreading is sometimes a
| deal breaker.
| kretaceous wrote:
| Exactly my thoughts. The only incompatibility with Bun is the
| unavailability of the `using` keyword:
|
| > If you are using Bun (which doesn't natively support using
| and uses a transpiler which is incompatible with this
| library)...
|
| I skimmed the issues but I couldn't find any issues on Bun
| regarding this except for: https://github.com/oven-
| sh/bun/discussions/4325
| W4G1 wrote:
| I added a bit more information about Bun compatibility:
|
| > While Bun is supported and Bun _does_ support the `using`
| keyword, it 's runtime automatically creates a polyfill for
| it whenever Function.toString() is called. This transpiled
| code relies on specific internal globals made available in
| the context where the function is serialized. Because the
| worker runs in a different isolated context where these
| globals are not registered, code with `using` will fail to
| execute.
| petesergeant wrote:
| Documentation here is exceptionally well written for a JS
| project, although move() doing different things depending on the
| type of data you pass to it feels like a foot-gun, and also how
| is it blocking access to arrays you pass to it?
| crabmusket wrote:
| It's part of the web platform:
| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
| steelbrain wrote:
| This is incredible! The SharedJsonBuffer got me all excited!
|
| Writing module bundlers in Javascript had diminishing returns
| from multi threading because of the overhead of serializing and
| deserializing ASTs.
|
| I wonder how far something like this would push the ceiling.
| Would love to see some benchmarks of this thing hauling ASTs
| around.
| rokob wrote:
| The implementation of the shared json buffer is nuts
| EdNutting wrote:
| From an overall system point of view, this is the current
| pinnacle of footgun design.
|
| The OS does thread management and scheduling, facilitates IPC,
| locking, etc. All of this is one big largely-solved problem (at
| least for the kind of things most people are doing in JavaScript
| today). But because of history, we now have a very popular
| language and runtimes that are trying to replicate all these
| features, reinventing wheels, and adding layers on inefficiency
| to the overall execution.
|
| Sigh.
| throwaway17_17 wrote:
| I don't disagree with you about the additional inefficiency
| that is very likely to accumulate as JS adds more and more
| 'features' (via the language, frameworks, or libraries). But as
| a genuine question, isn't this reimplementation (or any
| comparable library for multithreading) required by JavaScript's
| position on sandboxing. I would be suspicious of intent if
| browsers were allowed to spawn any number of threads to execute
| non-trusted scripts at the level typically seen from more
| native application code.
| EdNutting wrote:
| Allowing access to native threading doesn't imply that the
| API provided by the language is unrestricted. There is a
| (very wide) middle zone to land in.
| zarzavat wrote:
| I'm confused why drop() is a function that you have to import
| inside the closure instead of a method.
| W4G1 wrote:
| It was a design decision to make the syntax feel as familiar to
| Rust as possible. But I do agree that it's a bit verbose and
| that it won't hurt to add a .dispose() handle to the objects
| themselves.
| mnahkies wrote:
| I'd be interested to see a comparison with https://piscinajs.dev/
| - does this achieve more efficient data passing for example?
|
| Lack of easy shared memory has always felt like a problem to me
| in this space, as often the computation I want to off-load
| requires (or returns) a lot of data.
| geakstr wrote:
| This part is beautiful:
|
| > Serialization Protocol: The library uses a custom "Envelope"
| protocol (PayloadType.RAW vs PayloadType.LIB). This allows
| complex objects like Mutex handles to be serialized, sent to a
| worker, and rehydrated into a functional object connected to the
| same SharedArrayBuffer on the other side.
|
| It's kinda "well, yes, you can't share objects, but you can share
| memory. So make objects that are just thin wrappers around shared
| memory"
| nowaymo6237 wrote:
| I've played around with webworkers and just could never seem to
| get over the latency issues
| W4G1 wrote:
| Interesting. Are you talking about the latency to spawn new
| workers, or getting data from the main thread to the worker? To
| give you an idea, this library uses a lazily initialized thread
| pool (thread-per-core by default), where tasks are shared
| between workers (like the Tokio library in Rust). This means
| workers only need to be initialized once, and passing data via
| structured clone is usually very fast and optimized in most
| engines. Better yet is to use ArrayBuffer or SharedArrayBuffer,
| which can be transferred or shared between threads without any
| serialization overhead.
| nowaymo6237 wrote:
| It usually came from serializing and deserializing objects
| which here it's a shared json buffer? But even then there's a
| serialization bottleneck right? You'd have to be mindful
| about how the context and closures work across boundaries.
| Then there's also spinning up the workers, but I suppose you
| could do this ahead of time. Maybe my complaint is self-
| inflicted and is ultimately avoidable - but the complexity
| begins to mount.
|
| There's also the queuing and blocking nature of web-workers,
| I wish they could asynchronously process messages the same
| way js IO works, but that's not the case. Rather you are
| batching full units of work. The mental model is different.
|
| Anecdotally in Firefox I must have run into some memory leak
| issues and had to hard restart.
|
| Ultimately I ended up going with service workers, which yes
| sounds strange but I found to be much easier to work with.
| Cancellable requests, async, long living in the background
| ... but maybe it just works best for me ;)
| halapro wrote:
| Not "missing" at all. Stuff like this has been available for a
| decade, both as library and as compile-time optimization (which
| is arguably better)
|
| One such example: https://github.com/developit/workerize-loader
| chrisweekly wrote:
| Related tangent: Platformatic's NodeJS "Watt" server^1 supports
| parallelization with kernel-level load balancing across CPU
| cores. It looks like a game-changer for Node performance and
| efficiency in production. Apparently the new AWS "Lambda Managed
| Instances" do something similar (tho I'm short on details).
|
| 1. https://www.platformatichq.com/watt
| dwoldrich wrote:
| Hi @w4g1, this is a very beautiful API! I am curious if it
| supports my hypothetical use case.
|
| I want the pre-existing DOM thread to enter into a long term
| relationship with a particular web worker thread. I'd like the
| web worker to hold onto a transferrable that has ties back to the
| DOM thread, the WebGLRenderingContext, and have the DOM thread
| send draw commands to the worker and its gl context over time.
|
| I imagine this could be achieved by having a dedicated web worker
| pool of size 1 and allowing the DOM thread to initiate an async
| send to the worker pool.
|
| Can your API do something like this? Thank you for this early
| Christmas gift!
| dzonga wrote:
| wow!! beautiful api nicely made, yeah this should get to be part
| of standard library.
___________________________________________________________________
(page generated 2025-12-06 23:01 UTC)