6 ms·
Your comment seems to be conflating concurrency with parallelism. JS doesn't have any language-level abstractions for parallelism (async or not) but you do hav
by 3np 3y ago
Your comment seems to be conflating concurrency with parallelism.
JS doesn't have any language-level abstractions for parallelism (async or not) but you do have Web Workers[0] and process forking (depending on runtime) to get actual parallel programming. JS async deals with concurrency, not parallelism.
Threads are the opposite: They are interfaces for parallel programming and their use is orthogonal to how your application handles the resulting concurrency.
You say "the runtime/kernel take care of concurrency" - are you telling me you never write a mutex or implement locking logic? Because that's what "taking care of concurrency" is. I'd choose refactoring callback-/Promise-hell over debugging a complex deadlock any day (unless intra-process parallelism is actually a requirement, which may tip the scale in the other direction).
In the context of doing concurrency and parallelism in Rust, I'd 100% agree that the JS/C#-style async/await approach isn't necessarily always the best approach and it's good to consider alternative idioms if your requirements call for it. For anyone writing "apps" or glue-libraries, though, I'm thankful that they stay away from spawning threads all over my system by default and that they need more tools than "learn Rust async in 15 minutes" gives them to become dangerous.
Messing up your single-threaded event-loop concurrency can hog roughly a single CPU core and cause OoM. Messing up thread-based concurrency can have larger implications on the hosting system.
[0]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers...
- deleted 3y ago[deleted]
- ngrilly 3y agoasync/await doesn't entirely remove the need for mutexes and locks. We still need them if we have multiple coroutines using a shared resource across multiple yield points.
- pkolaczk 3y ago> We still need them if we have multiple coroutines using a shared resource across multiple yield points. We still need them if we have multiple parallel tasks (coroutines spawned non-locally) using a shared resource across multiple yield points. As long as the accesses to the shared variable are separated in time, sharing is fine. This is correct code: let mut foo = 1; async { foo += 1 }.await; foo += 1; println!("{foo}"); See - a shared variable used across multiple yield points. Another (more useful) example I showed below in another post with `select!`.
- gpderetta 3y agothe equivalent threaded code wouldn't need a mutex either: int foo = 1; std::thread ([&] { foo+=1; }).join(); foo+=1; std::cout <<foo <<'\n'; (sorry for the C++, I don't speak much rust).
- pkolaczk 3y agoPoint taken. What about this pattern (pseudo code, obviously it would require e.g. adding some code for tracking how much data there is in the buffer or breaking the loop on EOF, but it illustrates the point): mut buffer: &[u8] = ...; loop { select! { _ = stream.readable() => stream.read(&mut buffer), _ = stream.writable() => stream.write(&mut buffer), } }
- gpderetta 3y agoOne you add enough tracking meta data to to know how much there is in the buffer, you literally have implemented an SPSC queue.
- pkolaczk 3y agoWell, not really, because async/await guarantees I don't have to deal with the problem of producer adding data at the same time as consumer is removing the data in this case. In a proper SPSC queue some degree of synchronization is needed.
- gpderetta 3y agoYou stop adding data when the queue is full, you stop popping when it is empty. You need the exact same synchronisation for async, just different primitives.
- pkolaczk 3y agoBut that's not synchronization between two concurrent things. I can still reason about queue being full in a sequential way. select! { _ = channel.readable(), if queue.has_free_space() => read(&mut queue), _ = channel.writable(), if queue.has_data() => write(&mut queue), } The point is I can implement `has_free_space` and `has_data` without thinking about concurrency / parallelism / threads. I don't need to even think what happens if in the middle of my "has_free_space" check another thread goes in and adds some data. And I don't need to invoke any costly locks or atomic operations there to ensure proper safety of my queue structure. Just purely sequential logic. Which is way simpler to reason about than any SPSC queue.
- gpderetta 3y ago> Threads [...] are interfaces for parallel programming Threads are both for parallelism and for concurrency. Threads have been used for decades on machines without hardware parallelism. > are you telling me you never write a mutex or implement locking logic aside from the fact that mutexes vs futures is completely orthogonal to async vs threads, I definitely prefer dealing with mutexes. 99% of mutex usage is trivial and deadlocks are relatively easy to debug. The issue with locks is their performance implicaitons.
- usrbinbash 3y ago> Your comment seems to be conflating concurrency with parallelism. No, it really doesn't. I mention both concurrency and parallelism, and their main difference. > Threads are the opposite: They are interfaces for parallel programming No, they are not. Threads can do both. When waiting for an i/o bound operation, a thread can simply sleep. Added bonus: A thread basd implementation supports io bound concurrency and cpu bound parallelism using the exact same principle, and letting the kernel/runtime take care of the details. > are you telling me you never write a mutex or implement locking logic? Pretty sure I never said that. As for what I prefer to debug: Most Mutex-based synchronicity tasks that come up in practice are easy. And if "complex deadlock" does occur, it's usually pretty clear what resource was locked. Debugging that is just a question of going over all callers that access that resource. And as mentioned before, all that code is synchronous. So each one of them is easy to reason about. So yea, all in all, I prefer debugging problems arising from deadlocks over wading through callback-hell. By a huge margin. Oh, and all that is before we even talk about using CSP as an approach to synchronizing threads, which makes it both harder to mess up, and again easier to reason about.
- 3np 3y ago> It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it If the comment is not conflating concurrency and parallelism as you say, mind expanding on this part?
- nvy 3y agoBut concurrency is just "single core parallelism" anyways, so this isn't really germane to the discussion. JS has neither.
- Sinidir 3y agoConcurrency is not "single core parallelism". Concurrency describes tasks/threads of execution making independent progress of each other. Parallelism describes tasks/threads actually running at the same time.
- dboreham 3y ago> process forking (depending on runtime) to get actual parallel programming If you get into a time machine back to the 1980s, then yes.
- orangepurple 3y agoWeb Workers are not parallel - they are only concurrent to the main context: think OS threads. Async JS is akin to using very lightweight simulated threads. You will not necessarily utilize more CPU cores by spawning additional Web Workers because they are not inherenent parallel. The actual performance of Web Workers depend on how your browser and OS schedules threads. They are OS threads despite the mountain of misinformation on the Internet about them implying that they are truly parallel. They are not.
- sa46 3y agoI thought the primary purpose of web workers was that the browser can run the workers in parallel to the main thread. As the spec says: > [Web workers] allow long tasks to be executed without yielding to keep the page responsive
- orangepurple 3y agoThe workers don't block painting and they do not run in a separate process. That's why it's concurrent but not parallel. The web worker does work whenever the main thread is not painting and there is a free time slot. The browser is not painting all the time. You don't get extra calculation performance with web workers. You just create the illusion of a smooth experience because you don't block painting. It does not complete faster.
- sa46 3y agoThreads can certainly run in parallel with one another if the OS schedules them on different cores. I did a quick experiment and the main thread and worker threads run in parallel. https://github.com/jschaf/web-worker-experiment/ https://github.com/jschaf/web-worker-experiment/ > You don't get extra calculation performance with web workers The primary purpose of web workers is extra calculation performance. From MDN: > Workers are mainly useful for allowing your code to perform processor-intensive calculations without blocking the user interface thread
- 3y ago