- Запустите
go vet
в своем коде, и вы заметите, что он выдает предупреждение loop variable conn captured by func literal
, поскольку вы использовали conn
вместо переданного параметра, т.е. c
. Я исправил это в своем коде. Но это ортогонально вашей проблеме. - Давайте узнаем, как работает
select
(go -tour-5 , go -tour-6 ) :
* A select blocks until one of its cases can run, then it executes that case.
It chooses one at random if multiple are ready.
* The default case in a select is run if no other case is ready.
Таким образом, если вы используете select, не заключая его в for
l oop (например, select { ... }
), он может выбрать любой случай случайным образом, потому что несколько вариантов готов, или он может выбрать значение по умолчанию, потому что другой случай не готов. И select
блокируется до тех пор, пока не будет запущен один из его вариантов (включая значение по умолчанию), а затем он завершится. Но если вы заключите его в for l oop (например, for { select { ... } }
) и измените свою реализацию, вы можете запускать select до тех пор, пока он не попадет в тот случай, который вы хотите. И когда это происходит, горутина порождается, выходит.
Итак, то, что вы думаете, по сути, является заблуждением. Каждый прогон дает разный результат (иногда одинаковый); нет никакой гарантии, потому что select может выбрать любой вариант, если несколько вариантов готовы или если они не готовы, используется default
case.
Попробуйте эту программу и поймите, какие части я изменил:
package main
import (
"fmt"
"log"
"time"
)
func query(conns []int) string {
ch := make(chan string)
// receiver
go func() {
for m := range ch {
log.Println("message: ", m)
}
}()
// sender
for i, conn := range conns {
go func(c, loop int) {
log.Println("start: ", loop)
for {
select {
case ch <- get(c, loop):
log.Println("got: ", loop)
log.Println("exited: ", loop)
return
default:
log.Println("skipped: ", loop)
}
}
}(conn, i)
}
log.Println("wait")
time.Sleep(5 * time.Second)
return "done"
}
func get(i, loop int) string {
log.Println("process: ", loop)
return fmt.Sprintf("return loop: %d", loop)
}
func main() {
res := query([]int{1, 2, 3})
fmt.Println(res)
}
Go Playground Link
Вывод одного из прогонов:
2020/08/03 00:50:31 wait
2020/08/03 00:50:31 start: 0
2020/08/03 00:50:31 process: 0
2020/08/03 00:50:31 got: 0
2020/08/03 00:50:31 exited: 0
2020/08/03 00:50:31 start: 1
2020/08/03 00:50:31 process: 1
2020/08/03 00:50:31 skipped: 1
2020/08/03 00:50:31 process: 1
2020/08/03 00:50:31 skipped: 1
2020/08/03 00:50:31 process: 1
2020/08/03 00:50:31 skipped: 1
2020/08/03 00:50:31 start: 2
2020/08/03 00:50:31 process: 2
2020/08/03 00:50:31 skipped: 2
2020/08/03 00:50:31 process: 2
2020/08/03 00:50:31 skipped: 2
2020/08/03 00:50:31 process: 2
2020/08/03 00:50:31 skipped: 2
2020/08/03 00:50:31 process: 2
2020/08/03 00:50:31 skipped: 2
2020/08/03 00:50:31 process: 2
2020/08/03 00:50:31 skipped: 2
2020/08/03 00:50:31 process: 2
2020/08/03 00:50:31 skipped: 2
2020/08/03 00:50:31 process: 1
2020/08/03 00:50:31 skipped: 1
2020/08/03 00:50:31 process: 1
2020/08/03 00:50:31 skipped: 1
2020/08/03 00:50:31 process: 1
2020/08/03 00:50:31 skipped: 1
2020/08/03 00:50:31 process: 1
2020/08/03 00:50:31 skipped: 1
2020/08/03 00:50:31 process: 1
2020/08/03 00:50:31 message: return loop: 0
2020/08/03 00:50:31 process: 2
2020/08/03 00:50:31 got: 2
2020/08/03 00:50:31 exited: 2
2020/08/03 00:50:31 message: return loop: 2
2020/08/03 00:50:31 skipped: 1
2020/08/03 00:50:31 process: 1
2020/08/03 00:50:31 got: 1
2020/08/03 00:50:31 exited: 1
2020/08/03 00:50:31 message: return loop: 1
done
И обратите внимание, что я получил все return loop
сообщений, полученных горутиной-получателем.
РЕДАКТИРОВАТЬ : мы видим, что случай по умолчанию используется несколько раз горутинами, которые предполагают, что канал должен быть заблокирован (регистр не готов). Это связано с тем, что канал не буферизован.
Итак, ch := make(chan string)
можно изменить, сделав его неблокирующим по своей природе, сделав его буферизованным каналом. Вот ссылка на улучшенную версию на Go Playground .
package main
import (
"fmt"
"log"
"time"
)
func query(conns []int) string {
// buffered channel with a size of 3
ch := make(chan string, 3)
// receiver
go func() {
for m := range ch {
log.Println("message: ", m)
}
}()
// sender
for i, conn := range conns {
go func(c, loop int) {
log.Println("start: ", loop)
for {
select {
case ch <- get(c, loop):
log.Println("got: ", loop)
log.Println("exited: ", loop)
return
default:
log.Println("skipped: ", loop)
}
}
}(conn, i)
}
log.Println("wait")
time.Sleep(5 * time.Second)
return "done"
}
func get(i, loop int) string {
log.Println("process: ", loop)
return fmt.Sprintf("return loop: %d", loop)
}
func main() {
res := query([]int{1, 2, 3})
fmt.Println(res)
}
Результат одного из прогонов:
2009/11/10 23:00:00 wait
2009/11/10 23:00:00 start: 0
2009/11/10 23:00:00 process: 0
2009/11/10 23:00:00 got: 0
2009/11/10 23:00:00 exited: 0
2009/11/10 23:00:00 start: 1
2009/11/10 23:00:00 process: 1
2009/11/10 23:00:00 got: 1
2009/11/10 23:00:00 exited: 1
2009/11/10 23:00:00 start: 2
2009/11/10 23:00:00 process: 2
2009/11/10 23:00:00 got: 2
2009/11/10 23:00:00 exited: 2
2009/11/10 23:00:00 message: return loop: 0
2009/11/10 23:00:00 message: return loop: 1
2009/11/10 23:00:00 message: return loop: 2
done