8 ms·
Data races are impossible in JavaScript because it's single-threaded. Race conditions are possible in any language that can do anything asynchronous, which is b
by _bxg1 7y ago
Data races are impossible in JavaScript because it's single-threaded. Race conditions are possible in any language that can do anything asynchronous, which is basically all of them. But the general benefit you get from JS being single-threaded is the fact that any given callback is transactional. No other code will ever come in and mutate state between two regular lines of JavaScript code. Achieving this is pretty much the whole point of locking mechanisms, so under normal circumstances they aren't necessary for JS.
That said, when you use async/await instead of promises or callbacks, things do change because two sequential lines of code are no longer two truly sequential instructions. You're muddying the most fundamental metaphor of code syntax. That's why I personally don't like async/await syntax, although I get why people want it.
- Gaelan 7y ago> two sequential lines of code are no longer two truly sequential instructions How is this different from normal multithreading?
- eropple 7y agoNormal, which is to say preemptive, threading can pause your execution arbitrarily. You can wedge a NodeJS interpreter by not yielding, but you also have control over when that yielding happens.
- _bxg1 7y agoI suppose it's not, but it is different from normal JavaScript. I guess in multithreaded languages it's not such a big leap.
- gregmac 7y ago> two sequential lines of code are no longer two truly sequential instructions I've done a lot of async/await and I really can't think of any situations where this has been a concern for me. If you're mutating state in method calls without explicitly passing it around, that might be an issue but that's a deeper design issue IMHO.
- PetahNZ 7y agoOne use case I often find is a caching layer between JS and a HTTP call. If there are 2 calls to a non cached endpoint, the HTTP will fire off twice before caching the result. You can work around this by returning the first promise from the cache, but this is essentially a mutex. Having locking primitives would solve this and require less boilerplate code.
- kbenson 7y agoSeems like you should be able to build a lock mechanism fairly simply with async/await, but I agree, it would be nice if it was a built in primitive. It does feel a little silly to implement a lock function when the engine much be using one to support its async functionality in the first place, so you're introducing a lot of inefficiency.
- Arelius 7y agoI don't think it's true that the engine must be using a mutex to support it's async functionality. Or that it is true that an OS mutex will be any more efficient than alternative solutions. A mutex really can only exist to serialize async code. If you want it serialized that likely means that code shouldn't be async in the first place.
- kbenson 7y agoYou're right that it may not be required for the async functionality (especially since lock-free schedulers exist, and the engine is basically a single processor scheduler for processes). I do think it's likely in use other places though, just because it's much easier to write correct code with one.
- matharmin 7y agoImplementing a mutex is very simple and efficient in JavaScript - probably much lower overhead than the Promises itself.
- 7y ago
- desc 7y ago> But the general benefit you get from JS being single-threaded is the fact that any given callback is transactional. No other code will ever come in and mutate state between two regular lines of JavaScript code. Achieving this is pretty much the whole point of locking mechanisms, so under normal circumstances they aren't necessary for JS. The same claim can be made for Visual Basic. This always sounds like a stronger guarantee than it actually is. In practice, the exact same mistakes get made irrespective of what the runtime provides. Ultimately, people have to learn about race conditions in order to write correct code in a nontrivial system. Brushing the 'majority' of such cases under the carpet just makes it harder to educate them.
- burtonator 7y agoYou're sort of right but it depends on the races and the data structures. If you depend on a remote function and ordering is important lots of problems can happen. I've definitely needed more locking primitives and ordering but it's mostly for remote code. There are also a ton of bugs that can happen with memory allocation. For example if you do 50 async and they all come back and once and try to all allocate memory and then perform some action at the end you can have too many in flight and then use too much memory. It's not really threaded but you can run into similar problems. I've done 10+ years of core JVM threading with raw threads and softare transactional memory datastructures and CAS operations and my experience there helps a ton. Async is definitely not just 'free'
- nine_k 7y agoI think it's worth stopping to think in terms of "sequential lines of code". Even at CPU level, "sequential" instructions aren't, for a decade or so, to say nothing of xplicitly async code. One should think in terms of a dataflow graph, where data-independent nodes can run in any order, or in parallel. One should explicitly think about ordering of effects, and be explicit about effects in general. (Hence the rise of popularity of FP.)
- chrisweekly 7y ago>"be explicit about effects in general. (Hence the rise of popularity of FP.)" Yes - the benefits of pure functions in particular are increasingly evident.
- cellularmitosis 7y ago> Even at CPU level, "sequential" instructions aren't Within the context of the argument you are making, this is disingenuous. There's a big pile of transistors which determine whether it is safe to reorder those instructions.
- nine_k 7y agoThar same pile of transistors, with a thick layer of software, determines whether it's safe to proceed a coroutine. const foo = async () { const ice = await freeze(water); const wCream = await whip(cream); const base = await pour(liquor, ice); const cocktail = await put(base, wCream); return cocktail.serve(); } In the above fragment, `freeze` and `whip` may run in any order or in parallel, you don't get to choose. Still `pour` never runs before `freeze` (though it can complete before `whip`), and `put` can only run last. This is because the above is syntactic sugar, and the dataflow graph gets encoded in the promises graph, with `.then` clauses giving an unequivocal dependency order where applicable. Same in CPU: two loads can run in either order or in parallel, but an ADD that takes the result of both of them will only run when they both complete.
- esprehn 7y ago
- ihuman 7y ago> when you use async/await instead of promises or callbacks, things do change because two sequential lines of code are no longer two truly sequential instructions I thought async/await was just syntactic sugar for promises?
- h8hawk 7y agoNo it's not. async/await's semantic is similar to coroutines, and is implemented by them.
- ihuman 7y agoAre you sure? From what I can find, async [0] causes a function to a return a promise, which then returns the result. Await [1] takes a promise, and waits for it to either be resolved or rejected. [0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- matt-attack 7y ago> and waits for it Yes but that doesn’t mean other lines of code can’t be executing while waiting. The waiting isn’t truly blocking the entire application. Plenty of other things can be happening.
- deleted 7y ago[deleted]
- nemetroid 7y agoSure, but the next line of the function awaiting the result can't execute while waiting.
- h8hawk 7y agofrom your first link: "... , async/await is similar to combining generators and promises."
- tudorconstantin 7y agoSince an async function is simply a function that returns a promise is there actually any difference between using async/await and using promises explicitly?
- BigJono 7y agoNone, as far as I know
- dboreham 7y agoIt takes longer to boil your brain.
- eurasiantiger 7y agoTerseness and readability towards less asynchronously minded folk
- jinglebells 7y agoIf you're compiling to ES2016 or lower, it will turn the promises into state machines.
- zeta0134 7y agoEhh, I get where you're coming from, but await is at least explicit about it. Did you promise.then()? Did you call this function by putting an await in front of it? You yielded on purpose. If you need to keep hold of a resource on either side of that explicit action, either don't use either of those constructs, do but write a locking mechanism, or stop and think long and hard about why the lock is necessary. Granted, my practical experience is limited. I haven't really run into any of these kinds of situations outside of database work in Node, and there you've got the major benefit of transactions to do the locking for you, so the async JavaScript code doesn't have to particularly care and can yield whenever it wants. For me, the real benefit of await / async so far has mostly just been about improving code flow. Promises were already an excellent solution to the async problem, but their syntax for all but the most trivial example is something only a mother could love. async/await makes the code structure suddenly not necessarily look like callback hell, and in a lot of cases it's much more compact and easier to read. It greatly improves the chances that when I come back to it a month later, I won't have to reach back into the past and slap myself for writing that monstrosity. :)
- derangedHorse 7y agoI don’t understand how the async/await syntax changes anything. It’s really just syntactic sugar used to manipulate promises isn’t it?
- gregors 7y ago"logical" race conditions can still occur. Eg. 3 lines of code should run without interrupt. The inclusion of an async operation within this block now causes context switch (execution switch). I've had to debug this tricky situation in external libs before.