У меня есть несколько горутин, которые пытаются получить одновременно на одном канале. Похоже, что последняя горутина, которая начинает получать на канале, получает значение. Это где-то в спецификации языка или это неопределенное поведение?
c := make(chan string)
for i := 0; i < 5; i++ {
go func(i int) {
<-c
c <- fmt.Sprintf("goroutine %d", i)
}(i)
}
c <- "hi"
fmt.Println(<-c)
Вывод:
goroutine 4
РЕДАКТИРОВАТЬ:
Я просто понял, что это сложнее, чем я думал. Сообщение передается по всем горутинам.
c := make(chan string)
for i := 0; i < 5; i++ {
go func(i int) {
msg := <-c
c <- fmt.Sprintf("%s, hi from %d", msg, i)
}(i)
}
c <- "original"
fmt.Println(<-c)
Вывод:
original, hi from 0, hi from 1, hi from 2, hi from 3, hi from 4
original, hi from 4
...time.Sleep(time.Millisecond)
между отправкой и получением канала возвращает старое поведение.Ответы:
Да, это сложно, но есть пара практических правил, которые должны сделать все намного проще.
Вот альтернативная версия вашей программы, в которой применяются эти два правила. Этот случай демонстрирует, как много писателей и один читатель на канале:
c := make(chan string) for i := 1; i <= 5; i++ { go func(i int, co chan<- string) { for j := 1; j <= 5; j++ { co <- fmt.Sprintf("hi from %d.%d", i, j) } }(i, c) } for i := 1; i <= 25; i++ { fmt.Println(<-c) }
http://play.golang.org/p/quQn7xePLw
Он создает пять программ записи в один канал, каждая из которых записывает пять раз. Основная процедура чтения читает все двадцать пять сообщений - вы можете заметить, что порядок, в котором они появляются, часто не является последовательным (т. Е. Очевиден параллелизм).
Этот пример демонстрирует особенность каналов Go: можно иметь несколько писателей, совместно использующих один канал; Go автоматически чередует сообщения.
То же самое относится к одному писателю и нескольким читателям на одном канале, как показано во втором примере здесь:
c := make(chan int) var w sync.WaitGroup w.Add(5) for i := 1; i <= 5; i++ { go func(i int, ci <-chan int) { j := 1 for v := range ci { time.Sleep(time.Millisecond) fmt.Printf("%d.%d got %d\n", i, j, v) j += 1 } w.Done() }(i, c) } for i := 1; i <= 25; i++ { c <- i } close(c) w.Wait()
Этот второй пример включает ожидание, наложенное на основную горутину, которая в противном случае немедленно завершилась бы и вызвала бы досрочное завершение остальных пяти горутин (спасибо olov за это исправление) .
В обоих примерах буферизация не требовалась. Обычно полезно рассматривать буферизацию только как средство повышения производительности. Если ваша программа не блокируется без буферов, она не будет блокироваться и с буферами (но обратное не всегда верно). Итак, как еще одно практическое правило, начните без буферизации, а затем добавляйте ее позже по мере необходимости .
источник
main
функция, которая завершается, как только достигает конца, независимо от того, что делают другие горутины. В первом примере вышеmain
это синхронизированный шаг с другими горутинами, поэтому проблем нет. Второй пример также работает без проблем , поскольку все сообщения отправляются черезc
доclose
того вызывается функция , и это происходит прежде , чем вmain
goroutine завершается. (Вы можете возразить, что звонкиclose
в данном случае излишни, но это хорошая практика.)Поздний ответ, но я надеюсь, что это поможет другим в будущем, например, Длинный опрос, «Глобальная» кнопка, Трансляция для всех?
Effective Go объясняет проблему:
Это означает, что у вас не может быть более 1 горутины, слушающей 1 канал, и ожидать, что ВСЕ горутины получат одинаковое значение.
Запустите этот пример кода .
package main import "fmt" func main() { c := make(chan int) for i := 1; i <= 5; i++ { go func(i int) { for v := range c { fmt.Printf("count %d from goroutine #%d\n", v, i) } }(i) } for i := 1; i <= 25; i++ { c<-i } close(c) }
Вы не увидите "count 1" более одного раза, даже если есть 5 горутин, прослушивающих канал. Это потому, что когда первая горутина блокирует канал, все остальные горутины должны ждать в очереди. Когда канал разблокирован, счет уже был получен и удален из канала, поэтому следующая горутина в строке получит следующее значение счетчика.
источник
Это сложно.
Также посмотрите, что происходит с
GOMAXPROCS = NumCPU+1
. Например,package main import ( "fmt" "runtime" ) func main() { runtime.GOMAXPROCS(runtime.NumCPU() + 1) fmt.Print(runtime.GOMAXPROCS(0)) c := make(chan string) for i := 0; i < 5; i++ { go func(i int) { msg := <-c c <- fmt.Sprintf("%s, hi from %d", msg, i) }(i) } c <- ", original" fmt.Println(<-c) }
Вывод:
5, original, hi from 4
И посмотрите, что происходит с буферизованными каналами. Например,
package main import "fmt" func main() { c := make(chan string, 5+1) for i := 0; i < 5; i++ { go func(i int) { msg := <-c c <- fmt.Sprintf("%s, hi from %d", msg, i) }(i) } c <- "original" fmt.Println(<-c) }
Вывод:
Вы тоже должны уметь объяснять эти случаи.
источник
Я изучил существующие решения и создал простую широковещательную библиотеку https://github.com/grafov/bcast .
group := bcast.NewGroup() // you created the broadcast group go bcast.Broadcasting(0) // the group accepts messages and broadcast it to all members member := group.Join() // then you join member(s) from other goroutine(s) member.Send("test message") // or send messages of any type to the group member1 := group.Join() // then you join member(s) from other goroutine(s) val := member1.Recv() // and for example listen for messages
источник
Для нескольких горутин слушайте на одном канале, да, это возможно. ключевым моментом является само сообщение, вы можете определить такое сообщение:
package main import ( "fmt" "sync" ) type obj struct { msg string receiver int } func main() { ch := make(chan *obj) // both block or non-block are ok var wg sync.WaitGroup receiver := 25 // specify receiver count sender := func() { o := &obj { msg: "hello everyone!", receiver: receiver, } ch <- o } recv := func(idx int) { defer wg.Done() o := <-ch fmt.Printf("%d received at %d\n", idx, o.receiver) o.receiver-- if o.receiver > 0 { ch <- o // forward to others } else { fmt.Printf("last receiver: %d\n", idx) } } go sender() for i:=0; i<reciever; i++ { wg.Add(1) go recv(i) } wg.Wait() }
Результат случайный:
5 received at 25 24 received at 24 6 received at 23 7 received at 22 8 received at 21 9 received at 20 10 received at 19 11 received at 18 12 received at 17 13 received at 16 14 received at 15 15 received at 14 16 received at 13 17 received at 12 18 received at 11 19 received at 10 20 received at 9 21 received at 8 22 received at 7 23 received at 6 2 received at 5 0 received at 4 1 received at 3 3 received at 2 4 received at 1 last receiver 4
источник