7 ms·
Are there any languages that have really nailed this? I've used gevent, eventlet, (both python), promises, callbacks (node) and none of them come close to being
by tschellenbach 10y ago
Are there any languages that have really nailed this? I've used gevent, eventlet, (both python), promises, callbacks (node) and none of them come close to being as productive as synchronous code.
I'd like to try out Akka and Elixer in the future.
- jackweirdy 10y agoHow do you define "Productive"? Aside from that, personally I've used both Akka and plain Scala with Futures, as well as node with Promises, bare callbacks and async (though I've not tried fibers). I find Promises and Futures are the perfect balance between simplicity of use and the benefits of using the Async model. There's no need to reason about threads, as they abstract away the actual async implementation, and the interface they expose is very easy to reason about.
- mentat 10y agogoroutines with channels are well loved for concurrency in Go
- daurnimator 10y agoTry lua with cqueues http://25thandclement.com/~william/projects/cqueues.html http://25thandclement.com/~william/projects/cqueues.html
- quotemstr 10y agoC++? Java? Python? The traditional thread model isn't bad merely because it's traditional. I much prefer it to promise hell and to async-everything. About the only thing that beats it is CSP, which you can also represent sequentially without funky new keywords and which you can implement as a library for C++, Java, or Python. I never understood why people tout Go's goroutine feature so much. You can have it in literally any systems language.
- LukeShu 10y ago(I'm not terribly familiar with Python's threading, so I'm not going to talk about it) I never understood why people tout Go's goroutine feature so much. You can have it in literally any systems language. There are two big reasons for it. Firstly, goroutines are extremely lightweight. "Traditional" threading in C, C++, and Java means native OS threads, which are comparatively expensive. Sure, fiber/coroutine libraries exist for these languages, but they are far from common (and, the only fiber library for Java that I know of, Quasar, came after Go). Secondly, Go's ecosystem encourages CSP-style message-passing, rather than "traditional" memory-sharing. This is channels, not goroutines, but they make working with goroutines very nice. This is less concrete than the first reason; you certainly can implement message-passing in any of the other languages' threading styles. But empirically, it doesn't happen as often. A factor in this is also that, unfortunately, many CS curricula don't discuss CSP, which means that Go's use of this is the first exposure many programmers have to it.
- quotemstr 10y ago> But empirically, it doesn't happen as often. It's sad that people use choice-of-language as a proxy for choice-of-execution-strategy (interpreted? JITed?), choice-of-allocation-strategy, choice-of-linking-strategy, choice-of-packaging, and so on. All of these factors should be orthogonal. By linking them, we create a lot of inefficiency by fragmenting our efforts. AFAICT, C++ is the only language that's really been successful at being multi-paradigm.
- jerf 10y agoC++ still drives you very strongly in certain directions for the things you mentioned. Languages have to hand you very strong default choices for those things, because only the people with the hardest problems and the most time to solve them can afford to pick up a toolbox-box and build their own toolbox to solve a problem. Even the languages that arguably want to be that low of a level like Rust or D still have to offer a much more batteries-included standard library that will make more of those choices for you, and which will be for the vast majority of users the "real" version of that language.
- reality_czech 10y agoThe whole point of Golang is that every library and every project that uses Go will support coroutines and channels. Sure you can write a toy project in a language like C that has these concepts, but your toy library will effectively be usable with all of the other libraries that have ever been written for C. Any library that calls a blocking function will break your coroutine abstraction. It's like saying that indoor plumbing is no big deal-- it's just liquid moving through a pipe. Well yes. Yes, it is. But if you don't have plumbing in your neighborhood, or a sewage treatment plant in your city, you can't fake it by fooling around in your garage. And frankly, it's not going to smell like a rose.
- quotemstr 10y agoPeople get by just fine with wells and septic tanks, literally and metaphorically.
- reality_czech 10y agoI'm not sure if you're being sarcastic or not? Wells and septic tanks don't really scale if you have a lot of people living in an area. If too many people dig wells in the same neighborhood, the water table goes dry. Not to mention the need for everyone to buy and operate their own individual water softeners, water filters, and pumps. Septic tanks can leach into the ground (and probably into the aforementioned wells) or clog unless they're properly maintained at all times, by every neighbor. And someone needs to come by every single house and manually empty out the septic tank periodically. So yes... I guess it is a pretty good analogy for C++. Hundreds of stinky septic tanks, right near hundreds of wells, along with a guy arguing indoor plumbing is overrated. And a buggy, informally specified reimplementation of half of Common Lisp ;)
- btrask 10y agoI wrote such a library in C[1] and in practice it's been no problem. Most libraries that do IO provide hooks (for example I made SQLite fully async[2], with no changes to their code). For cases where that isn't possible (or desirable), there's also an easy way to temporarily move the entire fiber to a thread pool.[3] That's actually much faster than moving back and forth for every call (which is what AIO emulation normally entails). [1] https://github.com/btrask/libasync https://github.com/btrask/libasync [2] https://github.com/btrask/stronglink/blob/master/res/async_sqlite.c https://github.com/btrask/stronglink/blob/master/res/async_s... [3] https://github.com/btrask/libasync/blob/master/src/async.h#L178 https://github.com/btrask/libasync/blob/master/src/async.h#L... Disclaimer: not production ready, for most values of "production" Edit: stacks don't grow dynamically, of course. But that's also a problem in Go if you want to efficiently call C libraries. If you really need efficiency, you can use raw callbacks for that particular section.
- ezyang 10y agoI like to tell people that the killer app for Haskell is writing IO bound, asynchronous code. The secret weapon is do-notation, which lets you write code as if it were sequential, but have it desugar into what is (essentially) a series of chained callbacks. I like to point at Facebook's use of Haskell as a good example of being successful in this space http://community.haskell.org/~simonmar/papers/haxl-icfp14.pdf http://community.haskell.org/~simonmar/papers/haxl-icfp14.pd... It would be disingenuous to suggest that Haskell is good in all situations, but if there was one place where it should be used, this it.
- quotemstr 10y agoHaskell is good for lots of things, but I don't see it being particularly powerful in this application. The IO monad and do-notation let you write sequential code. So does Python. ¯\_(ツ)_/¯
- ezyang 10y agoThere are two responses here. The first is that in any language which has any sort of threading model at all, sequential code in multiple threads obviates the need for callbacks. If you block, fine; it's just a single thread, it's not obstructing the handling of other threads. Why don't Python and JavaScript have multithreading? Well, because programming with unrestricted concurrency and mutable state is really difficult. But there are ways to solve this problem, Haskell's purity being one among several. The second is that do-notation is distinct from the IO monad. Even if Haskell didn't have green threads in the runtime, I could still write an async/callback library that looked just as natural as sequential code. Why? It has nothing to do with the IO monad: it has to do with the fact that "do x <- e; m" desugars to, in JavaScript notation, bind(e, function(x) { m }); it's been "callbackified automatically".
- quotemstr 10y ago> could still write an async/callback library that looked just as natural as sequential code You can do that in any language with AST transforms powerful enough to transform code into CPS. That functions are "automatically callbackified" is an implementation detail and not one particularly germane to high-level code.
- xamlhacker 10y agoTry await/async in F#.
- HeyImAlex 10y agoWriting concurrent code in go takes a lot less thinking than js. Or... a different kind of thinking? But holistically I greatly prefer it for complex asynchronous code. Lack of generics on channels really hurts the library ecosystem though. Many things you need to write yourself.
- retrogradeorbit 10y agoErlang (and by extension Elixir and LFE) has "nailed" it by making the actor pattern first class. Go's channels are great, but Go itself is quite low level. Also you should checkout Clojure's core.async to see what improved channel constructs on top of a high level, lock-free, multithreaded language core looks like. Part of the problem with Python ecosystem is the insular mind set of its proponents. Python fanboys have no interest in going and seeing whats on the other side. So the platform has become a bit of an echo chamber with Pythonistas declaring their clunky approaches the industry best. You can see this by looking at how little love a CSP solution for python gets [https://github.com/futurecore/python-csp https://github.com/futurecore/python-csp] verses the enormous buy-in it's more popular frameworks receive.
- venantius 10y agocore.async is using locks under the hood - it's just hiding that from you as an implementation detail.
- retrogradeorbit 10y agoHow is it possible then that core.async works on javascript platform, a platform that has no mutexes? Maybe there is a lock to implement the thread macro (clojure only), but then that uses native threads. How would you propose to handle access to channels between native threads without locks? As far as I know there is no locking performed in asynchronous code implemented using the go macro. The go macro is a macro that turns your code inside out into a state machine, is it not? Each <! and >! point becomes and entry/exit into that state machine. There are no locks here because the go macro can essentially "rewrite" your source code for you and there is only a single thread of execution through the interconnected state machines.
- venantius 10y agoThere are obviously platform-specific implementations. In Clojure, core.async utilizes locks, and the original comment wasn't referring to ClojureScript.
- Matthias247 10y agoI've written quite a lot of concurrent code through the last years (network servers, protocol, ...) and overall I now like Go most. The biggest reason for this is not that necessarily that I think it has absolutely the best concurrency model, but that it's the most consistent one. Nearly all libraries are written for the model, which means they assume multithreaded access, blocking IO (reads/writes) and no callbacks. As a result most libraries are interoperable without problems. Erlang/Elixir should have similar properties - however I haven't used it. Javascript has a similar property because at least everything assumes the singlethreaded environment and concurrency through callbacks (or abstraction of them like promises and async/await on promises). I also like the interoperability and predictability here. But sometimes nested callbacks (even with promises) lead to quite a big of ugly code. And calling "async methods" is not possible from "sync methods" without converting them to async first (which could mean some big refactoring). So I prefer the Go style in general. The worst thing from my point of view are all the languages that do not have a standard concurrency model, e.g. C++, Java, C#, and according to this article also Python. Most of them have several libraries for (async) IO which can be beautiful by themselves but won't integrate into remaining parts of the application without lots of glue code. E.g. boost asio is nice, but you need a thread with an EventLoop. If your main thread is already built around QT/gtk you now need another thread and then have 2 eventloops which need to interact. Some question for Java frameworks, e.g. integrating a Netty EventLoop in another environment (Android, ...). In these languages we then often get libraries which are not generic for the whole language but specific to a parent IO library (works with asio, works with asyncio, ...) and thereby some fragmented ecosystems. A standard question that also always arises in these "mixed-threaded" languages when you have an API which takes a callback is: From which thread will this callback be invoked? And if I cancel the operation from a thread, will it guarantee that the callback is not invoked. If you don't think about these you are often already in bug/race-condition land.
- lmm 10y agoI use Scala without Akka. Just straightforward Futures and for/yield. It's great: the distinction between "=" and "<-" is minimal overhead when writing, but enough to be visible when reading code. You have to learn the tools for gathering effects (e.g. "traverse"), but you only have to learn them once (and they're generic for any effect, rather than being specific to Future, you can use the exact same functions to do error handling, audit logs and the like).
- mi100hael 10y agoAfter using Akka-HTTP, I never want to write a HTTP service with anything else.
- lmm 10y agoakka-http is nice. akka-actor (i.e. the project that was originally called "akka") is awful. The name overlap is unfortunate.
- jscholes 10y agoIn your opinion, what's wrong with akka-actor?
- lmm 10y agoIt sacrifices type safety without offering enough value to be worth that - especially given that the model also eliminates useful stack traces. It forces non-distributed applications to pay the price of an API designed for distributed ones. Its FSM model doesn't offer the conciseness it should.
- junke 10y agoFor Common Lisp, see lparallel and lfarm. https://lparallel.org/overview/ https://lparallel.org/overview/
- junke 10y agoFor Common Lisp, see lparallel and lfarm. https://lparallel.org/overview/ https://lparallel.org/overview/
- qznc 10y agoConcurrent ML according to Andy Wingo. He recently wrote a good series on concurrency in programming languages: https://wingolog.org/tags/concurrency https://wingolog.org/tags/concurrency
- RX14 10y agoSeriously check out crystal. Go's goroutines seem to do quite well, and crystal is pretty close to go in terms of concurrency, but is a higher-level language overall.
- deleted 10y ago[deleted]
- dhd415 10y agoI'm surprised there aren't more mentions of Tasks in C# or F# on the .NET platform as examples of asynchronicity done well. From the perspective of uniformity and availability, while C# provided asynchronicity via callbacks before the introduction of Tasks in the 4.5 release of the .NET Framework, all the core libraries that used callback-style async (as well as some that had been strictly synchronous-only) were updated with Task-based overloads, so there are no problems with Task-based async being inconsistently available. Additionally, adoption of Task-based async in third-party libraries has been high, so it's relatively uncommon to encounter code that does not support it. From the perspective of code productivity, it's hard to get much better than simply adding the async and await keywords where necessary. As a very simple example, consider a typical server application that receives requests via HTTP, processes them via an HTTP call to another service as well as a database call, and then returns an HTTP response. The sync code (blocking with a thread-per-request model) might look something like this: void handleRequest(HttpRequest request) { var serviceResult = makeServiceCallForRequest(request); var databaseResult = makeDatabaseCallForRequest(request); sendResponse(constructResponse(request, serviceResult, databaseResult)); } In order to make that same process async (non-blocking with a dynamically-sized thread pool handling all requests), the code would look like this: async Task handleRequestAsync(HttpRequest request) { var serviceResult = await makeServiceCallForRequestAsync(request); var databaseResult = await makeDatabaseCallForRequestAsync(request); await sendResponseAsync(constructResponse(request, serviceResult, databaseResult)); } It could even be taken one step further to make the service request and database call concurrently if there were no dependencies between the two which would reduce processing latency for individual requests: async Task handleRequestAsync(HttpRequest request) { var serviceResultTask = makeServiceCallForRequestAsync(request); var databaseResultTask = makeDatabaseCallForRequestAsync(request); await sendResponseAsync(constructResponse(request, await serviceResultTask, await databaseResultTask)); } I've added asynchronicity into a C# server application as above with substantial improvements in both individual request latency and overall scalability. I'm now working on a Java8 system and bemoaning the comparatively primitive and inconsistent async capabilities in Java8.