7 ms·
One thing to note is that async programming like in JavaScript is "threadless" programming which means all your data-structures are by definition thread-safe.
by galaxyLogic 9y ago
One thing to note is that async programming like in JavaScript is "threadless" programming which means all your data-structures are by definition thread-safe.
It's one less complication in your logic when you don't have to think "what if some other thread modifies this data while I am doing it?". And you never get hard-to-detect and hard-to-fix errors caused by that nor do you need to try to set up locks that might cause performance problems.
So some might say that threads and locks and synchronization primitives are the solution while others might say they are the problem.
- RHSeeger 9y agoWorth noting that it is possible to have threads without the complications of concurrent modification. You just need to pass things between threads as immutable values. Tcl does it this way (prior to having threads added, it used only an event loop, like JS).
- deleted 9y ago[deleted]
- tyingq 9y agoJS webworkers add a sort of multithreading. They force an internal copy, or force a transfer of ownership of anything you postMessage() over.
- mistercow 9y agoNote though, that many of the problems that come up with threads can still come up with single-threaded async, especially when building a server. As soon as you await, any object visible to another promise chain can potentially change out from under you, and this can cause analogous problems to multithreading. One example where this can get hairy is if you have a database connection shared between two promise chains running "in parallel". You'll get undefined query ordering, which can cause very bizarre behavior. Don't get me wrong: it's still easier. But there's an interesting property I've noticed of statements of the form "by definition, problem X is impossible when you have Y", which is that X-analogous problems absolutely can happen, and they are often more complicated to solve. For another example: "by definition, memory leaks are impossible when you have a garbage collector".
- foota 9y agoI once had to write a lock for JavaScript, it was beautifully simple.
- vanderZwan 9y agoYou're making me curious enough to want to see it. Would it fit in a post here or on a short gist?
- HeyImAlex 9y agoIt can just be a boolean variable called locked?
- deleted 9y ago[deleted]
- deleted 9y ago[deleted]
- nitely 9y agoAlmost. In threaded programming a thread will wait/block until the lock is released and then make use of the protected resource. In async world you would probably just check some flag and if it's "locked" then register a callback to some "unlocked" event to mimic this.
- amelius 9y ago"Promises" are a lot like locks.
- anonacct37 9y agoLookup twisted deferred locks. https://twistedmatrix.com/documents/9.0.0/api/twisted.internet.defer.DeferredLock.html https://twistedmatrix.com/documents/9.0.0/api/twisted.intern...
- m12k 9y agoIMO thread programming via locks, mutexes and such is in the same category as assembly programming - they're necessary low level primitives that are extremely easy to get wrong, and hence they should ideally be left to specialized experts, who can use them to develop higher level abstractions (e.g. worker pools, event queues, tasks) that normal application developers can use for most of their use cases.
- tr1ck5t3r 9y agoThreaded programming is best thought of as independent programs which can interact with other programs without the hassle of dealing with unknown or to-be-learnt communication methods like pipes, DDE, COM, poke/peek and so on. Its more of an issue now a days as there are many multi-core cpu's unlike the old days where a cpu was typically single core and everything ran like clockwork. With multi-core multi threaded programs you are just picking ways for these threads or independent programs to wait for other parts to catch up and vice versa.
- yashap 9y agoAgreed. I've been programming primarily in Scala the last few years, which has great high-level concurrency abstractions, and the code honestly stays readable and easy to reason about. We get the odd concurrency-related bugs, but they're reasonably rare, no more common than people screwing up things like time, text encoding, not properly using db indexes, etc. Thread-based concurrency need not be overly scary. Any concurrency model will take some time to wrap your head around, but with the good ones, once you get comfortable with them, they should feel very safe, intuitive and deterministic. Scala's approach of "Futures + immutability when you don't need mutable state, or Actors when you do need mutable state" has worked well for every problem I've encountered in the last few years, and I've certainly heard great thing's about Go's approach too. ES6 has improved the state of concurrency in JS, it's now similarly easy to read and reason about as Scala's model, but the lack of actual parallelism is a pretty big limitation - often a deal breaker for server-side code, and still often an issue client-side.
- hannofcart 9y ago> but lack of actual parallelism is a pretty big limitation - often a deal breaker for... Well, in my experience, it has been a deal breaker in a select few, rare cases. But often? Do you routinely come across problems that could NOT be solved using a multiprocess approach (in Node) or webworkers on the client side? Could you share some of these scenarios?
- deleted 9y ago[deleted]
- konschubert 9y agoStupid question. Does this mean that a web server written in node is running single-threaded? I know that there are callbacks and promises to hand over control between different execution flows. But doesn't running on a single thread put an upper limit on the amount of work a server can handle? And, if the solution is to spin up more servers with access to the same database, doesn't that mean that we are now having multiple threads accessing the database concurrently? Much like, say, Python Django?
- nitely 9y ago> Does this mean that a web server written in node is running single-threaded? Yes. > But doesn't running on a single thread put an upper limit on the amount of work a server can handle? Is not about how much work it can handle, it's about how much it can offload. Async servers can handle a much greater volume of I/O bounded tasks. So it can handle more connections. When the task is CPU bounded you can either create a thread (which does not really scale well) or offload it to some other servers that can scale horizontally (i.e doing micro services) > And, if the solution is to spin up more servers with access to the same database, doesn't that mean that we are now having multiple threads accessing the database concurrently? Much like, say, Python Django? Yes, to take advantage of all CPU cores you have to create more server instances. But why would they talk to the same database instance? It could be a replica or a shard. You can even have a pool of shards connections per server instance.
- amelius 9y agoThe problem with offloading to a thread is that you can't structurally share data with another thread. So any copying of e.g. arguments can be very expensive, and awkward/inconvenient as well.
- nitely 9y agoI agree. But like always, trade-offs. > The problem with offloading to a thread is that you can't structurally share data with another thread. That's really a JS issue, not a general async programming issue, though.
- bad_user 9y agoWith asynchrony you get out of order execution and non-determinism by definition. That you don't have the concerns of shared memory concurrency, that's actually not such a big relief, especially given the right abstractions. And the underlying problem, that you need to deal with concurrency, doesn't go away. I've just written an article describing how JavaScript's Promise leaks memory in "then" chains and that it shouldn't: https://alexn.org/blog/2017/10/11/javascript-promise-leaks-memory.html https://alexn.org/blog/2017/10/11/javascript-promise-leaks-m... The reception was actually negative, having lots of people telling me that I'm concerned about a niche use-case, but functional programming isn't a niche at all, being one of the two known ways to write reasonable, composable code that deals with concurrency and asynchrony, the other way being to have a draconian borrow checker, like that of Rust. Mainstream developers are in general deluding themselves that the tools they are using are adequate, in spite of repeated evidence to the contrary, whereas most current breakthroughs in productivity were born out of functional programming, starting from garbage collection, but also actual libraries that people are now fond of, like React. Haskell does shared memory concurrency. The JVM / Scala does that too. In Haskell you work with things like IO, MVar, STM (transactional memory) and many more. High level abstractions that have been borrowed by Scala as well, my current languages of choice. I feel much more confident writing concurrent Haskell or Scala code, with 0% test coverage, then I am writing JavaScript code with 100% code coverage.
- rdtsc 9y ago> all your data-structures are by definition thread-safe. It is thread safe from data being accessed by two different threads, well because there is only one thread running. But surprisingly it is not free from application level data races in general, and you might still need async mutexes or lock type constructs. That is because it is possible to have multiple callback chains each partially modifying the same data structure, or trying to write to the same tmp file, or modify some global resource. In general with callback programming each callback chain specifies a sort of a poor man's IO concurrent context of execution. In a thread you spawn a thread to execute some code in a function, so it executes instructions A, B, then C. And there can be another thread executing the same A, B, C instructions in parallel. With callbacks it's the same idea, a chain of A, B, C callbacks can execute IO requests in parallel. Two client request coming almost at the same time, would run through A, then B, then C functions. A, B, might be called by first request. Before it finishes, and calls C, another request might start A, B chain again.