Каналы и тупиковые каналы
Я пытаюсь понять язык Go. Я попытался создать две гортины
которые соединяют поток между ними, используя два канала:
func main() {
c1 := make(chan int)
c2 := make(chan int)
go func() {
for i := range c1{
println("G1 got", i)
c2 <- i
}
}()
go func() {
for i := range c2 {
println("G2 got", i)
c1 <- i
}
}()
c1 <- 1
time.Sleep(1000000000 * 50)
}
Как и ожидалось, этот код печатает:
G1 got 1
G2 got 1
G1 got 1
G2 got 1
....
До выхода основной функции.
Но если я отправлю другое значение на один из каналов из основного, он вдруг блокирует:
func main() {
c1 := make(chan int)
c2 := make(chan int)
go func() {
for i := range c1{
println("G1 got", i)
c2 <- i
}
}()
go func() {
for i := range c2 {
println("G2 got", i)
c1 <- i
}
}()
c1 <- 1
time.Sleep(1000000000 * 1)
c1 <- 2
time.Sleep(1000000000 * 50)
}
Он выводит
G1 got 1
G2 got 1
G1 got 1
G2 got 1
G1 got 2
а затем блокируется до основных концов.
Значение "2", отправленное в c1, поступает в первую goroutie, которая отправляет ее на c2, но вторая
goroutine никогда не получает.
(Использование буферизованных каналов с размером 1 (либо c1 или c2) работает в этом примере)
Почему это происходит? Когда это происходит в реальном коде, как я могу его отладить?
Ответы
Ответ 1
Каналы Go, созданные с помощью make(chan int)
, не буферизуются. Если вам нужен буферный канал (который не обязательно будет блокироваться), сделайте его с make(chan int, 2)
, где 2 - размер канала.
Что касается небуферизованных каналов, так это то, что они также синхронны, поэтому они всегда блокируются как для записи, так и для чтения.
Причиной этого является то, что ваш первый goroutine ждет завершения его c2 <- i
, пока второй ждет завершения c1 <- i
, потому что в c1
появилась дополнительная вещь. Лучший способ, который я нашел, чтобы отлаживать подобные вещи, когда это происходит в реальном коде, - это посмотреть, какие блокировки заблокированы и много думают.
Вы также можете обойти проблему, используя только синхронные каналы, если они действительно нужны.
Ответ 2
nmichaels справедлив в своем ответе, но я подумал, что добавлю, что есть способы выяснить, где вы зашли в тупик при отладке такой проблемы.
Простой, если вы работаете с Unix-подобной ОС, запустите команду
kill -6 [pid]
Это убьет программу и даст трассировку стека для каждой версии goroutine.
Немного более привлекательный способ заключается в подключении gdb.
gdb [executable name] [pid]
Вы можете проверить стек и переменные активного горутинца как обычно, но нет простого способа переключения goroutines, о котором я знаю. Вы можете переключать потоки ОС обычным способом, но этого может быть недостаточно, чтобы помочь.