I have the feeling I keep running into deadlocks most of the time I try to use channels in Go. I have the feeling I can't keep the mental model on how to correctly signal the closing of the channel. Do you guys have any advise or tips to get it right ?
评论:
riking27:
The language basically requires that you have a single piece of code responsible for closing a given channel. Anything else is prone to panics.
If you're having trouble figuring out:
- when multiple producers are all exhausted: use a sync.WaitGroup
- how to abandon pending items in the channel (when client goes away): use a
done chan struct{}
, such as provided bycontext
earthboundkid:
sync.Once
is also extraordinarily useful here.
sybrandy:The reason that double closing a channel causes a panic is that a design where multiple goroutines can close a channel is subject to a race condition. The language is trying to force you to use a better design for your concurrency. Once you’ve done it correctly once or twice, it gets easier to reason about.
nsd433:I believe the recommendation by Dave Cheney is that the sender is who closes the channel. This simplifies things greatly because since it's what will be sending on the channel, it can make sure you don't try to send on a closed channel.
deadlocks
If communication flows two ways between two goroutines, via two channels, then you have one goroutine send and receive normally, and the second goroutine has to always be willing to yield while it is blocking wait to send to the first.
In other words, the 1st goroutine can do
chan_r1_to_r2 <- msg
while the 2nd goroutine must do
sent: for { select { case chan_to_r1 <- msg: break sent case msg2 := <- chan_r1_to_r2: // process or pend msg2 and go back to trying to send msg (if still applicable) } }
This is the same sort of idea as sorting your mutexes so they are always locked in a certain order to avoid deadlocks.
Sometimes the design can be changed so that the job done by r2 can be split into an r2 which receives, and an r3 which sends. That results in cleaner code as long as the locking between r2 and r3 is minimal.
The wrong way is to have r2 do
go func(msg) { chan_r2_to_r1 <- msg }
because once several of these are pending there's no controlling the order in which they send to the channel.
