react-native-community / discussions-and-proposals

Discussions and proposals related to the main React Native project
https://reactnative.dev
1.69k stars 127 forks source link

Multithreading Workers on React Native? 🤔💭 #486

Closed gomes42 closed 3 days ago

gomes42 commented 2 years ago

Introduction

Within the react-native community some projects have already tried to bring things like this, creating a bridge of JavaScript code that runs in a parallel abstract thread, unfortunately many of the projects have been discontinued or not given enough attention.

Details

Related projects to this discussion:

Discussion points

I don't know if there is any NodeJS abstraction of WebWorkers that works on mobile, but if we look closely at mobile browsers, both Android and IOS has webworkers support, there must be some useful code to work on top of that somewhere.

UPDATE: JSI seems to be the solution but abstracting all of the WebWorkers code used at Browsers/NodeJS doesn't make sense thinking React Native's architecture. A cross-platform solution sharing similar code could be to implement something like react-native-multithreading did (using JSI / std::thread) on mobile and WebWorkers (see inline workers) on the web.

UPDATE: May we can take inspiration from the work of NativeScript (Offloading Tasks to Worker Threads with NativeScript)

ricardotech commented 2 years ago

We must put some effort working at multithreading in RN!

ospfranco commented 2 years ago

Why would you want this?

WebWorkers are a web API, all web APIs have heavy restrictions with them in order to comply with isolation and security features. In the case of web workers, you have things such as not being able to manipulate the DOM, not having access to the window object, and such.

On the other hand, RN is not a web browser, it just runs JS. Web APIs are implemented on the browser and not on the JS engine. Trying to copy from a browser directly might entail way too much work and it anyways a waste of time, because what you care about is API compatibility, not the exact same implementation. All the web APIs you use on react-native (e.g. setInterval) have been implemented separately for react-native but they are not the same implementation as their browser counterparts.

The final question would be, what exactly are you trying to achieve that mrousavy/react-native-multithreading cannot achieve? if you are desperate for web workers you can create a similar API on top of that, but it's an apple to oranges comparison. There are certain things the browser will be able to achieve because it is integrated into the OS which RN will not be able to achieve, even if somebody would go over the lengths to implement web workers as close as possible to the web specification. At the end of the day what you care about is off-loading heavy computation away from the main thread, everything else is just quirks and implementation-specific details.

mrousavy commented 2 years ago

Agreed with Oscar.

What you're looking for should probably be done on the native side, as that's where you have native threading primitives.

gabrieldonadel commented 2 years ago

Is your focus cross-platform maintainability(web/mobile)? I guess if that's the case you could use just wrap mrousavy/react-native-multithreading and Webworkers into a common API, although it would be limited I believe you would be able to do most things with a simple wrapper.

gomes42 commented 2 years ago

Why would you want this?

WebWorkers are a web API, all web APIs have heavy restrictions with them in order to comply with isolation and security features. In the case of web workers, you have things such as not being able to manipulate the DOM, not having access to the window object, and such.

On the other hand, RN is not a web browser, it just runs JS. Web APIs are implemented on the browser and not on the JS engine. Trying to copy from a browser directly might entail way too much work and it anyways a waste of time, because what you care about is API compatibility, not the exact same implementation. All the web APIs you use on react-native (e.g. setInterval) have been implemented separately for react-native but they are not the same implementation as their browser counterparts.

The final question would be, what exactly are you trying to achieve that mrousavy/react-native-multithreading cannot achieve? if you are desperate for web workers you can create a similar API on top of that, but it's an apple to oranges comparison. There are certain things the browser will be able to achieve because it is integrated into the OS which RN will not be able to achieve, even if somebody would go over the lengths to implement web workers as close as possible to the web specification. At the end of the day what you care about is off-loading heavy computation away from the main thread, everything else is just quirks and implementation-specific details.

First of all, thanks for your attention @ospfranco. I agree with your perspective. Many people end up relating React and React Native a lot since most APIs are extremely similar, but in essence these technologies are very different things, perhaps the name "Webworker" wouldn't even make sense thinking of a native side. My initial idea would be to apply multithreading in 3D graphics APIs that work both on the web and native with the same calls but I believe that the right thing to do, as @gabrieldonadel said, is to create a common API that uses WebWorkers on the web and another implementation (like @mrousavy's react-native-multithreading) on native.

gomes42 commented 2 years ago

Anyway I would still love to see React Native bring an official solution related to multithreading / multiprocessing...

bleszerd commented 2 years ago

Please note that from personal experience, react-native-multithreading does not work on Android currently

mrousavy commented 2 years ago

Please note that from personal experience, react-native-multithreading does not work on Android currently

react-native-multithreading is also just a proof of concept.

Screenshot 2022-06-20 at 16 20 55

Real Multithreading in JS requires some changes to the runtime, such as a thread-aware garbage collector that puts locks on object lifetimes, threading and locking primitives like Lock, Mutex and Thread, awaiting threads, referencing vs copying objects, SharedArrayBuffer, and much more. It might work, and it would be really cool if Hermes would add those primitives to the JS Runtime since they already have an async GC, but it just takes much more effort to create that - and I didn't even talk about the bundler yet.

We are researching true multithreading in JS for the JSI List ("WishList") project we build at Margelo, so we can render React views and func components completely on the UI Thread, this will probably also help Fabric and React concurrent rendering so it can separate the workload efficiently resulting in an overall much faster app because business logic (JS) and view logic (UI) could be run on separate threads - but this is just a theory.

Also Multithreading can be quite dangerous and people will misuse or abuse it. JS is simple because it's singlethreaded.

fgarzonhz commented 2 years ago

Or can implement a Ract-Native-Threads ... where can have some limitations like webWorkers ( arround the API render )... but can be a powerfull tool in words of process some information in paralell.

gomes42 commented 1 year ago

See the related discussion on https://github.com/react-native-community/discussions-and-proposals/discussions/528#discussioncomment-4346367

joeky888 commented 1 year ago

Why would you want this?

Using comlink and comlink-fetch for heavy http requests and websockets in a separate thread. Targeting react-native-web is also a reason.

The Qwik team introduced a goroutine-like approach called worker$(): Source

<button
  onClick$={worker$(() => expensiveStuff())}
>
  Run in web worker!
</button>
spsaucier commented 1 year ago

May also be interested in worklets: https://github.com/margelo/react-native-worklets-core/blob/main/docs/USAGE.md

pleerock commented 7 months ago

Non of the listed packages actually work properly. react-native-worklets-core seems the least outdated, but comes with big limitations, and can be used only in particular scenarios.

Having threads / processes support is essential for such powerful platform like React Native.

I have a complex application which has lot of non-UI computations and my app is super slow and I simply don't know what to do with that. Non of exist solutions work. I can't go native, since I re-use lot of my JavaScript code (with computations) between browser, node.js and RN).

mrousavy commented 7 months ago

I wonder what your use-cases are, can you give an example on such "heavy" JS computations that you need to run?

findhumane commented 7 months ago

The simplest use case is network calls. As far as I understand and experienced, only one network call can run on the main async JS thread, so stuff like pre-fetching, analytics, and so on are risky especially with large payloads. I created a simple native library just for this use case: https://github.com/findhumane/react-native-threaded-downloader

import { performThreadedDownload } from 'react-native-threaded-downloader';

// First argument is the URL and second argument is the timeout in seconds
performThreadedDownload("https://example.com/", 60)
  .then((response) => console.log(response))
  .catch((e) => console.dir(e));
pleerock commented 7 months ago

I wonder what your use-cases are, can you give an example on such "heavy" JS computations that you need to run?

simple use cases might easily become "heavy", depend on application logic. In my case, I have a privacy-oriented application which encrypts/decrypts every byte of data on every server interaction. Such "server interactions" happen quite often in the app.

mrousavy commented 7 months ago

@findhumane I think in your case, the native implementation of fetch should just be tweaked to allow for multiple Threads. I personally didn't know that fetch only uses one Thread, I always thought it was a Thread-pool (concurrent dispatch_queue or Executor on Android), but I didn't test that.

@pleerock that's the only other example I could think of where you actually need to do computation on the client side, but we built react-native-quick-crypto and react-native-bignumber to move all of those computations to the native side (C++) where it can be handled much more efficiently - and in some cases even asynchronously using Threads. This uses the industry standard OpenSSL implementation. If there are other algorithms implemented in pure JS, then yes, you stand before the decision to either rewrite it to native code (ObjC/Java or even pure C++), or offload it to a Worklet context (you can also use our library here: react-native-worklets-core) - but keep in mind that switching to a different context (Worklet Thread), and then calling back with a result (await Promise) does take some time - and if your computation doesn't at least take the same amount of time to actually execute, the overhead is higher than the benefit and it might be counter-productive to spawn a secondary Thread anyways.

pleerock commented 7 months ago

@mrousavy cryptography can be an example which easy to argue about, but there can many other examples, you can depend on third-party libraries which can have different computations.

Just imagine you call every x milliseconds a function with lot of domain logic consist of simple computations like loops, variable assignments, function calls) and also makes function calls to a different complex third-party libraries. Everything might const of simple computations, but when called too often due to some domain requirements can cause performance issues. Something that can be efficiently taken into a separate thread.

In my case, I need more advanced cryptography and the only JS library I know which solve all my requirements is noble-curves. noble-curves doesn't use native bindings which isn't the most efficient thing to have, but works good for me, because codebase is highly reused across different platforms - same code works for browser, nodejs and react-native. And the amount of code I need to refactor is quite huge and I actually need a separate team to do that and use separate native implementations on different platforms. I have no resources for that, but if I had a threads support I could solve my problem fast and relatively long term.

I tried to integrate react-native-worklets-core into my project but since it doesn't support async I couldn't easily do that. Ideal scenario for me is to be able to execute code on a separate thread and have bi-side communication between main/UI and worklet threads.

spsaucier commented 7 months ago

The use-case we particularly need it for is multi-photo resizing and upload. This process is intensive and should be easy to offload to side threads.

mrousavy commented 7 months ago

Multi Photo resizing is something that is done on the native side. That already uses a different Thread.

And uploading is also happening on the native side. That is also a native Thread.

You can just use Thread-pools in the native plugin that you use to resize such images or upload them - but I really think those are already pooled - do you have a playground or repro where it does not properly multithread? I highly doubt that this is blocking anything.

a-eid commented 7 months ago

@mrousavy I think this is a much needed api, something like Native Script Workers API would be perfect.

keep in mind that flutter also have Concurrency and isolates

I don't see why RN shouldn't have something similar.

tremblerz commented 7 months ago

I suppose any heavyweight computation needs offloading to a different thread. For example, I have an app that performs a lot of statistical computation and stores the result on local storage. Before I was using runOnJS from reanimated and it was completely hanging the interface but with react-native-worklets-core I can spawn the computation and forget about it.

ricardotech commented 7 months ago

Maybe implementing that using native workers to handle the threadings should work fine with multiple http requests being handled, a FIFO algorithm should also be implemented in order to provide the correct execution order on the worklets.

markwitt1 commented 3 days ago

Any update on this? Is there a a best practice for this with Websockets?