12 ms·
I love this about Go. All functions are simple and synchronous, but if you want to call them concurrently, just "go SlowThing()" and coordinate with a channel.
by benhoyt 6y ago
I love this about Go. All functions are simple and synchronous, but if you want to call them concurrently, just "go SlowThing()" and coordinate with a channel. Compare that to the async stuff in C#, Python, etc -- it's bolted on later, and you see double of everything.
- bcrosby95 6y agoYou're just using the channel as a future. None of this is really problematic at the top level view of things. But when you need to compose libraries or applications that make use of these things - yes, even channels in Go - you can start running into problems. Especially if you don't actually control the process you're running in. This is why promises and async/await really exist. E.g. if you have code you need to fork off the main UI thread so you don't block it, but need to re-capture the UI thread so you can call UI update code when you're done with your long running task. async/await is so much cleaner here. Async/await allow for much more complex control of flow than simple goroutines and channels. Sometimes it's necessary. Sometimes it isn't. It was necessary in nodejs. It's extremely useful in applications where you have a "main thread" that drives your application that you don't want to unnecessarily block. Go doesn't solve this problem out-the-box. There's a reason why c# adopted async/await despite having futures and all sorts of other tools.
- philosopher1234 6y agoI don’t see how futures can be more flexible than go routines. Could you explain that more? And why couldn’t you just spawn a goroutine to avoid blocking your main thread?
- deleted 6y ago[deleted]
- jayd16 6y agoSometimes you want to block the main thread, or at least finish what you were doing. With async/await and cooperative concurrency you can be explicit about what will run on a thread. You retain control of the thread until you yield or await. You can ask tasks to complete on other threads or post back to the main thread. You have a lot of control. Its easy to write code without locks that runs concurrently on the main thread but is still able to build a UI in a threaded way. I don't really know how go UI frameworks work. How do you have multiple, preemptively scheduled goroutines on the UI thread but without critical sections? You have to use channels and message passing back to a main thread manager to handle this, yes? I think Java's Loom would have the same issue but again, I don't really know. Perhaps worrying about a UI thread is 'fighting the last war' and we should work on new UI paradigms in these new language features.
- anonymoushn 6y agoYou can spawn a goroutine that immediately waits on a channel, so that it will not actually do anything until you want it to. This seems at least as expressive as a single-threaded switch() primitive. I think it can also express the threaded async pattern you want, but I'm not sure.
- jayd16 6y agoAre there any popular, idiomatic Go UI frameworks I could explore?
- oefrha 6y agoYou can try your luck with https://github.com/avelino/awesome-go#gui https://github.com/avelino/awesome-go#gui I explored the landscape earlier this year when I was building a GUI version of a CLI tool written in Go. I was throughly disappointed by all the “native Go” and non-webview options — narrow selection of widgets with basic functionality missing, UIs ranging from lack of polish to horrendous looking (that makes Java GUIs look like godsend), lots of bugs, etc. I ended up using the Qt binding which, despite its own share of problems, at least worked fairly reliably and didn’t constantly get in my way in every way imaginable: https://github.com/therecipe/qt https://github.com/therecipe/qt
- deleted 6y ago[deleted]
- deleted 6y ago[deleted]
- throwaway34241 6y agoI'm working on a UI app in Go, yes I think you'd use channels and message passing but it doesn't look all that bad in practice. First you'd probably call runtime.LockOSThread() to tie a goroutine to an OS thread if the native API you're coding against needs that (like Cocoa, OpenGL, etc). To perform work on the main thread using closures is probably the most convenient way. So you just have a RunOnMainThread(func(){...}) that puts the closure on a channel to run on the main thread (or uses some similar feature from the underlying native framework). It's not terribly inconvenient - it's similar to the old Cocoa / UIKit performSelectorOnMainThread: method except that you have closures to make things simpler. There's probably a little syntactic overhead compared to if you had several async/await functions running concurrently on the main thread. But on the other hand it's probably a bit easier to reason about, since the flow of execution on the main thread is very straightforward (if you have multiple async/await functions running at the same time on the main thread, it seems like every time they await a result they'd have to worry about other code being run on the main thread, and make sure they release any locks, etc).
- bcrosby95 6y agoThe point of async/await is to make code that exits and returns to some kind of execution context - without blocking either context - read like plain, synchronous code. A simple example of where this might be useful is in UI code. Every UI framework I've worked in has a single UI thread, where all UI changes must go - e.g. adding a button, adding text to the UI, etc. At the same time, you don't want to block the UI thread, because that will cause the UI to become unresponsive. A common thing you might do in a UI is: on button click, make an API call, then update the UI with that data. The on button click will be called from the UI thread. You don't want to make the API call there though because that would block the UI thread. So you spin it off into a worker thread. But after that worker finishes, you need to regain control of the UI thread so you can update the UI. The way you traditionally do this is you have some kind of main loop which is running the UI thread, which has (among other things) some kind of queue of functions it should call (I assume it would be a channel in Go). So your worker thread will hold onto a reference to this queue. When it finishes, it pushes a function call onto the queue. The main UI loop occasionally checks this queue and calls all functions in it. So eventually it gets called, allowing you to update the UI in the UI thread. This can be fine for simple scenarios. But if you have deeply nested or complex interactions between multiple threads, the code can get confusing and turn into "callback hell". This is the problem that was ran into in early versions of nodejs. This is the problem async/await was intended to solve. It's a bigger problem in nodejs due to its design, but it can still be useful in other languages that don't share that design depending upon the application you're writing.
- kjksf 6y agoA channel can be used as future / promise but that's not what it is and that's not how it's used by majority of Go programs. Channel is what it says it is: a way to send data between goroutines. Also, Go works just fine for UI code, out of the box. See https://github.com/lxn/walk https://github.com/lxn/walk for one of many examples. You just lock main goroutine with runtime.LockOSThread() to its thread and that's your UI thread and marshall code that touches UI to UI thread. Which is the same thing you must do in C# (or any other language). See https://github.com/golang/go/wiki/LockOSThread https://github.com/golang/go/wiki/LockOSThread
- BiteCode_dev 6y agoAsync def are not colored functions in python. In fact there are not function at all, just like generators are not. Also coroutines can be called from functions and vice versa. You don't have to choose. The problem is not the color. The problem is the default paradigm is to be blocking I/O, and you add non blocking on top. It's way easier to make everything non blocking, then add blocking things on top. It's not a matter of syntax, but of core paradigm. Go and erlang were designed from the non blocking perspective from the get go. But even go add to use something to manage async control flow, so it uses channels, which you don't need if you have coroutines. Erland went deeper and made everything immutable, and baked in actors in the runtime. To me that's more impressive.
- anonymoushn 6y agoCan you expand on your notion of what is and is not a colored function? I might use this test: When I wish to add a call to a function that yields control other than by returning to the middle of a regular function, so that the latter portion of the regular function can use the results of yielding, do I then have to change the function declaration and every single call site? You clearly disagree. What test should we use?
- BiteCode_dev 6y agoIt's like calling classes colored functions because you can both call them. Classes are not functions. They are classes. Coroutines are not functions, they are coroutines. Different primitives. A function has a single exit entry point and a single exit point and no state. If you call a function, you run its body. A coroutine has several entry points and exit points, and an internal state. If you call a coroutine, you make an instanciation (the body doesn't run). You can argue than using coroutine for async handling has drawbacks, but colored functions is not the proper analogy and make people very confused about the whole thing. The fact the syntax to define them is so similar doesn't help, and I see most people difficulties comming from their attempt to reconciliate models that have no reason to be. Just like a hashmap and an array are 2 different things despite you can index both, functions and coroutines must be understood as separated concepts.
- tsimionescu 6y agoYou can actually do the same thing as `go SlowThing() ` in C#, though it has more boilerplate. For example, you can do `Task.Factory.StartNew(() => SlowThing())`. The thing is, Go doesn't have async functions, because it doesn't have await. That is why you don't have colored functions in Go: it doesn't support anything as advanced. Sure, the runtime does really cool things under the hood, but the Go programming model is more like Threads than async/await, because goroutines can't return data and they can't throw exceptions.
- dullgiulio 6y agoGo does not have exception (exactly because of this problem.) For passing information, you use channels, which can pass more than one value back to the caller. The big thing is not running tasks in the background but the tight integration of channels and runtime scheduler that allows having an invisible event loop on top of what is synchronous programming.
- tsimionescu 6y agoI think there are many reasons why Go doesn't use exceptions, though this is possibly one of them. Channels are nice, but they are not that hard to replicate in other languages (probably less efficiently). They have their uses, but they can't completely supplant shared memory or async/await - all 3 are nice to have in different situations.
- jayd16 6y agoAgreed. Await seems much nicer for the case of some sleep/wait causing subroutine that returns something. var foo = await bar(); vs c1 := make(chan float64, 1) go bar(c1) foo := <-c1 Sure you have to be in an async method to use await but its really not that bad if you embrace it. Go seems better suited for high throughput message passing.
- philwelch 6y agoExcept in Go you could just as easily call: foo := bar() and if bar() is a function that does something annoying or time-consuming before finishing its work and returning the value, it just behaves like a normal, synchronous function call. “Await” is, as TFA explains, syntactic sugar for slapping an async function into behaving like a normal synchronous function call. In Go there is no reason you can’t just write your function calls synchronously in the first place.
- hombre_fatal 6y agoGo routines just act like threads. Something you can spawn in any language with the same guarantees. Even C/C++ have channel libraries. Channels just easily introduce complexity and channel-hell which is why they aren't all that popular as abstractions. Even in Go. Frankly, I have a hard time imagining too many seasoned developers clutching channels to their chest as something superior to async/await. Async code with channels is not simple, you have in-channels, out-channels, and channels-over-channels. It's a pretty niche instrument working best when you only have uni-directional communication in your application. For example, I've worked on channel code where I'd rather be debugging mutex deadlocks.
- ghostwriter 6y agoAsync stuff in Python can be encoded in multiple ways, with gevent everything is a regular function. http://sdiehl.github.io/gevent-tutorial/ http://sdiehl.github.io/gevent-tutorial/
- aaai 6y agoEXACTLY! But... don't bother too much trying to explain this to ppl who don't intuitively grok it right away... some just seem to never get it no matter how hard you try and explain it to them, it's like their brains are "wired differently" when it comes to reading and understanding code, they don't get the advantage and meaning of unification / universality / "one solution for many problems" etc. Go is NOT my favorite programming language, but there's a stroke of simple genius in it that probably only got materialized bc its creators were left alone to work on it their way inside Google and just implemented their solution to things without being bothered by "language experts"... EDIT+: not saying "colorless + channels" is always better or anything like that, all's tradeoffs... probably async/await code is much more readable than channels hell for many/most cases (but because it's less powerful - no true parallelism possible).