Итак, давайте посмотрим, что действительно происходит в вашем источнике. У вас есть два goroutines (их более двух, но мы сосредоточимся на явных), main
и readFromChannel
.
Давайте посмотрим на то, что readFromChannel
делает:
if channel `c` is not empty before `ti` has expired, print its contents and return, after signalling its completion to wait group.
if `ti` has expired before `c` is not empty, print "TIMED OUT" and return, after signalling its completion to wait group.
сейчас Main:
adds to waitgroup
make a channel `c`
start a goroutine `readFromChannel`
sleep for 5 seconds
send 10 to channel `c`
call wait for waitgroup
Теперь, давайте идти через поток исполнения для кода, одновременно (ваш код может/не может выполнить в этом порядке каждый раз, помните об этом)
1) wg.Add(1)
2) c := make(chan int)
3) go readFromChannel(c, time.After(time.Duration(2)*time.Second))
#timer ti starts#
4) time.Sleep(time.Duration(5) * time.Second)
#MAIN Goroutine begins sleep
#timer ti expires#
5) case <-ti:
6) fmt.Println("TIMED OUT")
7) wg.Done()
# readFromChannel Goroutine returns #
#MAIN Goroutine exits sleep#
8) c<-10
9) ......#DEADLOCK#
Теперь вы можете указать почему вы зашли в тупик. В режиме небуферизованных каналов будет блокировать, пока что-то не произойдет на другом конце канала, независимо от того, отправляете или получаете ли вы. Таким образом, c <- 10
будет блокироваться, пока что-то не прочитает с другого конца c
, но у вас был горутин, который у вас для этого выпал 2 секунды назад. Поэтому c
блоки навсегда, а с main
- последний горутин слева, вы получаете Тупик.
Как это предотвратить? При использовании каналов убедитесь, что на другом конце канала всегда есть receive
для каждого send
. Вы также можете использовать буферный канал, но в вашем коде выше это не будет «правильным» решением.
Вот мое исправление тупика:
func main() {
wg.Add(1)
c := make(chan int)
go readFromChannel(c, time.After(time.Duration(2)*time.Second))
time.Sleep(time.Duration(5) * time.Second)
c <- 10
wg.Wait()
}
func readFromChannel(c chan int, ti <-chan time.Time) {
// the forloop will run forever
loop: // **
for {
select {
case x := <-c:
fmt.Println("Read", x)
break loop // breaks out of the for loop and the select **
case <-ti:
fmt.Println("TIMED OUT")
}
}
wg.Done()
}
** see this answer for details
Это один из самых полезных ответов, которые я читал в то время. Просто чтобы убедиться, что я следую: «исправление» работает, потому что он удерживает канал приемника даже после таймаута. Итак, 'wg.Done()' (и прекращение процедуры 'main' go) будет когда-либо происходить, если что-то читалось с' c', правильно? –
Правильно, но чтобы немного проясниться, он держит ** goroutine ** включенным. – AJPennster