Channels are a pretty highly debated topic in the Go community, the most notable complaints (that I've thought about at least) being the difference between a buffered/unbuffered channel and how that blocks sending/receiving with the channel.
Before going into it I'll explain the difference between the two.
A buffered channelis a channel that only has x number of slots, which are set when the channel is declared. e.g: ch := make(chan int, 3) declares a channel with 3 slots in it. It can be looked at almost like a stack, it does not block until the channel is full. One can produce asynchronously until the channel is full.
Starts a consuming goroutine to consume from the channel
Produces 5 messages to the channel, outputting when it is trying to produce
The sub-thread consumes as it can, but the main thread can only produce two ints before hanging on the third
The sub-thread consumes after three seconds and the program runs to completion
Now this seems pretty simple since it is like writing to a stack or queue that has a limit. Things start to get interesting once we move onto...
An unbuffered channel is much the same as a buffered channel minus the fact that it blocks on consume until the message is received for every message (unless the channel is closed). This can be looked at as a pipe that can just continually be written to or read from, with
This one is a bit harder to understand so it may be better to just run and inspect the output as you read the steps I have below.
Like before, making a channel (unbuffered) and starting a consumer goroutine.
Immediately start producing to the messages, the consumer thread receives 2 of them, then decides to take a break for 3 seconds, which causes the main thread to hang.
Once the sleeping is done the consumer catches up and consumes everything left by the main thread as the loop runs to completion.
Personally, unbuffered channels are much easier to understand IMO. If one goroutine produces a message to a channel, it hangs until a different goroutine picks it up. Easy.
Unbuffered channels are the nicest from the point of view that they can have multiple routines consuming them, so it is easy to scale batch processing etc. Here is a code example where I spin up five workers to consume off of the same channel! https://play.golang.org/p/xeXb_1a7YUn
So what was the point of this blog post other than a channel tutorial?
I actually really enjoy the fact that you can range over channels so 3rd party (or your own!) libraries can just return a channel which easy to loop over. We do that for kafka consumers at work.
A buffered channel is a pretty easy way to limit the number of workers on a system. Rather than using sync.WaitGroup another way is just to use a buffered channel since the channel will block on send once the channel is full, thus limiting the number of "in-flight" workers. Of course the other way is just to use a buffered channel and fire up n workers based on a configuration or something.
Now I'm thinking about sync.WaitGroup again. I should write something about it. I enjoy a LOT that it will block until workers are completed (would be nice for the example snippets I have here).
Channels feel sort of actor-ey to me, much like Erlang/Elixir processes that receive messages. Though processes over there are buffered with a default "mailbox size" that is the size, and it will overflow and crash if too many messages are waiting to be processed. But it is the same concept. It's nice to be able to create a worker thread in Go that kinda just does things and maintains its own state (read as: I like actors and you should too).
Anyways, thanks for reading my ramblings. I'm over 1k words already so I should probably end this one here!