Unfortunately, some Go people reach for the "this is not idiomatic"-cudgel far too quickly, instead of actually looking at the various trade-offs.
They are not universally a bad thing, but total avoidance of sync package primitives is a bad idea (and much slower in many circumstances)
This gives a good analysis:
https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-s...
> Now that goroutine needs to be closed, then you end up with an additional close channel
Goroutines that just map values or similar will usually share a context with some other goroutine and can therefore reuse that context's close channel.
So, having a context is still "end[ing] up with an additional close channel" as silasdavis said. Even if you pass it to something that will be cancelled like a network operation, you still must correctly notice and handle the resulting timeout error.
The value for context is almost entirely that everybody in the community has come to agree on it rather than its functionality per se. Which is the same as io.Reader, in that the utility isn't its functionality, which any number of languages can replicate, but the way everybody agrees to implement and use it through the entire ecosystem, which is where other languages have a lot more trouble. Nothing technically stops that from happening, but you often end up with islands of agreement in different major frameworks or library ecosystems instead of ecosystem-wide agreement. Context is everywhere now; it's actually been a few months since I encountered a place I wish that took it that didn't.
> More on-topic, channels have their own tradeoffs. I often reach for WaitGroups and mutexes instead of channels, because things can get complicated fast when you're routing data around with channels
You're absolutely right. I certainly didn't intend to give a blanket recommendation. It's more of a, "If you're sharing memory, might it become clearer if you share memory by communicating?" I was worried that the simplistic examples would not properly represent the cases I was thinking of. I think that's a communication error on me.
If I run it locally it's not consistent:
go run lock/main.go
[3 0 1 2 5 4 6 9 7 8] 10⏎
go run lock/main.go
[1 5 6 7 8 0] 6⏎
go run lock/main.go
[0 3 1 2 4 5 6 7 8] 9⏎
Here's a version that does work for me: https://play.golang.org/p/b6bRb9pgIGZThe issue is that appending to slices concurrently is not safe, so you have to use a lock around the append or similar.
https://play.rust-lang.org/?version=stable&mode=debug&editio...
I'm a beginner at rust, but this is the version I came up with that compiles and works:
https://play.rust-lang.org/?version=stable&mode=debug&editio...
I found this helpful: https://youtu.be/29LLRKIL_TI?t=1340
> Map is like a Go map[interface{}]interface{} but is safe for concurrent use by multiple goroutines without additional locking or coordination. Loads, stores, and deletes run in amortized constant time.
Also that for most uses a concurrent map is way overkill, and a thread-safe one is both costly and basically useless (hence the Java folks not keeping the thread-safety when migrating from Hashtable to HashMap).
On the other hand they're kinda shit given how awful non-builtin data structures are in Go, and how easy it is to "leak" maps between goroutines.
Be careful relying on the results of the Go playground; it has a bunch of differences and probably has GOMAXPROCS set to 1, which most other systems will not.
You need a sync.Mutex or similar protecting your call to append :)
var wg sync.WaitGroup
n := 10
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
defer wg.Done()
doSomething()
}()
}
wg.Wait()
You still have to call wg.Done(), unfortunately. People have written small wrappers around sync.WaitGroup that change the interface: var wg bettersync.WaitGroup
n := 10
for i := 0; i < n; i++ {
wg.Go(func() {
doSomething()
})
}
wg.Wait()You would need to be careful that once one goroutine has sent a pointer to a channel, it doesn't touch that buffer again (until the receiver has finished with it, at least), otherwise you can get data races. You're implementing ownership semantics here, and unlike in some languages i could name, you're doing it without help from the type system.
Edit: add what one gets when run with `go run -race`
> WARNING: DATA RACE > Read at 0x00c0000a6000 by goroutine 8: > runtime.growslice()
Adding a print(len(ints)) at the bottom of the function:
More on-topic, channels have their own tradeoffs. I often reach for WaitGroups and mutexes instead of channels, because things can get complicated fast when you're routing data around with channels...more complicated than sharing memory. I don't think it's good advice to broadly recommend one over the other--understand their tradeoffs and use the right tool for the job at hand.