For the purpose of simplicity, we've designed most of our applications and sample code with bidirectional channels, but of course any channel can be set unidirectionally. This essentially turns a channel into a "read-only" or "write-only" channel.
If you're wondering why you should bother limiting the direction of a channel when it doesn't save any resources or guarantee an issue, the reason boils down to simplicity of code and limiting the potential for panics.
By now we know that sending data on a closed channel results in a panic, so if we have a write-only channel, we'll never accidentally run into that problem in the wild. Much of this can also be mitigated with WaitGroups
, but in this case that's a sledgehammer being used on a nail. Consider the following loop:
const TOTAL_RANDOMS = 100 func concurrentNumbers(ch chan int) { for i := 0; i < TOTAL_RANDOMS; i++ { ch <- i } } func main() { ch := make(chan int) go concurrentNumbers(ch) for { select { case num := <- ch: fmt.Println(num) if num == 98 { close(ch) } default: } } }
Since we're abruptly closing our ch
channel one digit before the goroutine can finish, any writes to it cause a runtime error.
In this case, we are invoking a read-only command, but it's in the select
loop. We can safeguard this a bit more by allowing only specific actions to be sent on unidirectional channels. This application will always work up to the point where in the channel is closed prematurely, one shy of the TOTAL_RANDOMS
constant.
When we limit the direction or the read/write capability of our channels, we also reduce the potential for closed channel deadlocks if one or more of our processes inadvertently sends on such a channel.
So the short answer to the question "When is it appropriate to use a unidirectional channel?" is "Whenever you can."
Don't force the issue, but if you can set a channel to read/write only, it may preempt issues down the road.
3.17.176.72