5 ms·
For me the most puzzling aspect of Go is channels. Ada tried CSP (Concurrent Sequential Processes that Go channels are based on) in eighties and people quickly
by _0w8t 5y ago
For me the most puzzling aspect of Go is channels. Ada tried CSP (Concurrent Sequential Processes that Go channels are based on) in eighties and people quickly realized that it lead to bad performance and was unsuitable for a lot of useful cases. So Ada got standard mutexes and signals.
So why CSP which does not allow to implement priority delivery or multicasting and makes cancelling much harder compared with normal polymorphic message queue per thread? Moreover, Go crippled CSP even further as one cannot select on channel and file descriptor.
Fortunately Go does provide mutexes and signals so one can ignore channels unless required by Go API. Still without proper sum types polymorphic and type-safe message queues are not possible.
- pkulak 5y agoI can never seem to understand why a GCed language has pointers, and makes you memorize when to use stack vs heap.
- mftb 5y agoIt's pretty straightforward, knowing when to use the stack vs heap can improve your performance. Having both paradigms available in the language means you can start out relying on the GC for everything. Later when you're ready, you can look to make more intelligent use of the stack.
- bashery 5y ago+1
- hnov 5y agoI think it's at least partially about value vs (mutable) reference semantics in Golang's case and the hope is that the compiler will know to whether heap or stack allocate based on escape analysis.
- Laremere 5y agoGo uses escape analysis to ensure memory safety of pointers. Taking a reference of a local variable means it might end up on the heap, and calling new might allocate on the stack. It all depends on if Go can determine that the lifetime of the pointers is less than the stack frame. It's good if you don't want to care about those details, it's bad if you really care about those details for performance reasons.
- foldr 5y ago>and makes you memorize when to use stack vs heap You don't have to think about stack vs. heap allocation in Go unless you're writing performance sensitive code. You could pretend that Go heap allocates everything and still read and write Go code without any problems.
- lawl 5y agoHits the sweet spot for me between the one extreme of having to malloc/free everything myself and the other extreme where everything is immutable, garbage collected and you dont know what causes allocation. The former is a nightmare for security and productivity, the latter is a nightmare for performance.
- int_19h 5y agoGo can implicitly heap-allocate even a local variable if you e.g. capture it in a closure. This is actually more implicit than you get in Java or C#.
- morelisp 5y agoJava can decide to heap-allocate or not heap-allocate at runtime based on what optimizations the JIT has performed so far, and so it can change over the course of a program's run. I don't think you can get more implicit than that.
- int_19h 5y agoWhy would Java ever heap-allocate a local, regardless of optimizations? It can decide to heap-allocate or stack-allocate objects (when you new them) based on escape analysis, which is a JIT optimization. But that's very different from heap-allocating variables.
- morelisp 5y agoThen I'm not even sure what you're talking about when you say "heap-allocate even a local variable" if you don't actually mean the value of that variable. If you draw that distinction, "variables" are never allocated; spaces for values are. Regardless, whether something is a local or not in Java still varies depending on the JIT's decisions. (Or rather, the JIT does not see local/non-local variables, only lifetimes, and whether the lifetime may extend past the JIT's current view.)
- 5y ago
- deleted 5y ago[deleted]
- pjmlp 5y agoPerformance, Java made the mistake to not have good ways to deal with GC free allocations and now who knows when Valhala will ever happen. Thankfully plenty of other GC based languages hadn't made the same mistake.
- zozbot234 5y agoGo has shared memory and mutexes, but using them means giving up on memory safety because the whole rigmarole that Rust does to preserve safety in concurrent code is totally missing in Go. So that's a meaningful reason to stick to CSP - sure, it might create easy deadlocks but at least it won't totally crash your app with a potentially exploitable fault.
- _0w8t 5y agoGo could have provided message queues with priorities Erlang style or something closer native solutions in Linux/Max/Windows that are known to work. They are safe, allows to avoid deadlocks just as channels and much more flexible. And with mutex one can build those as necessary, even if interaction with channels is ugly.
- sriram_malhar 5y agoErlang doesn't have priorities natively, does it? As far as I am aware, there is a standard pattern to do it, like this: receive {high_p, Message} -> process_high(Message) after 0 -> receive {high_p, Message} -> process_high(Message); {low_p, Message} -> process_low(Message) end end The Go equivalent can be done with different channels for priorities and similarly nested selects, as I am sure you are aware.
- _0w8t 5y agoHm, is this pattern the best Erlang has and VM does not optimize it to avoid the need to lock the queue twice? If so then you are right double-select is equivalent even if it is rather messier in Go then in Erlang with the need to provide 2 channels.
- ergl 5y agoErlang has selective receive, meaning it will go through all your receive branches in order, until one matches. With this code: receive {high_p, Message} -> process_high(Message); {low_p, Message} -> process_low(Message) end if the process has one high_p message and a low_p message, the high_p message will be processed first.
- coder543 5y ago> Still without proper sum types polymorphic and type-safe message queues are not possible. This isn't really true. You can define a channel where the message type is an interface, and then you can use a type switch on the receiving side to downcast that interface to various concrete message types. What you can't do without sum types is ensure that you don't need a "catch-all" branch for if/when a value is sent down the channel that isn't one of the concrete message types you expect, but it will still be type safe and polymorphic.
- _0w8t 5y agoBy type-safety I indeed also implied inability to send messages that the receiver would not process. This is essential to allow safe refactoring.
- randomswede 5y agoWhat I have done in the past when I've needed to pass various different types across a channel is to look at what the receiver of these messages actually need to do, factor that out into an interface, make a "chan myInterface", then make sure that every data type I need to pass across actually implements that interface. The main thing I am working on (slowly, because time and stuffs) is a client library for an saync-protocol messaging/forum platform, where each request will (eventually) receive a response, so I send the request, then construct a request-specific data type to pass across to the listener, together with the ID of the request, so that when the response returns, it can dispatch to the proper decoder for the response. That actually seems easier to describe in code, than in words, but this margin is too short for that.
- bsaul 5y agoThat’s a weird definition of type safe… with very very low standards. What OP probably means is that there’s no way to build a channel transmitting a list of various types while having the compiler warn you if you’ve forgotten a possible type, or if you’re trying to unwrap into an impossible one.
- 5y ago
- avl999 5y agoAvoid channels except for straight forward, producer-consumer workflows. Use mutexes and other traditional concurrency primitives for other stuff. Channels are a huge shotgun with caveats.
- gilgad13 5y agoI agree, with the addition that closing an unbuffered `<-chan struct{}` is a good way to do broadcast notifications (a. la. the context package). As evidence that channels should be used rarely, and only in small scopes, consider the number of places chan is used in the standard library.
- lanstin 5y agoThe thing about channels is, while they might be slow for tight inner loop type compute code, it lets you scale across all the cores very linearly. And, given a channel to flow the work thru, you can manage the number of workers at each stage however you like - growing with more load, dropping work with more load, back pressure with more load. All concisely and explicitly. And a lot of network server things are classic producer/consumer things, so it’s great to have a solution for that where you get concurrency without any thread worries.
- bashery 5y ago+1
- Zalastax 5y agoYou may find my MsC thesis, link in profile, interesting! If I remember my writing correctly, I have similar reflections on CSP.
- deleted 5y ago[deleted]
- chombier 5y agoMaybe the addition of generics could enable Concurrent ML-style composable channels? This wouldn't solve the performance issue, but at least it would provide a nice abstraction for protocols.