Неблокирующий канал в блоге "Go Concurrency Patterns: Timing Out, moving on" - PullRequest
0 голосов
/ 02 августа 2020

Я прочитал этот блог о том, как получить данные из нескольких подключений, и попытался дать ему понять, как это работает. * внутри блока select он будет печатать все сообщения (ожидаемый результат).

Но если я использую неблокирующий канал, не комментируя случай default, канал печатает только одно сообщение (и другие будут печатать «пропущенное» сообщение журнала). Я не могу понять, почему входящие сообщения приходят на по умолчанию . Есть ли шанс, что все сообщения будут go в регистре по умолчанию? Я думал, что все сообщения будут напечатаны, потому что функция get немедленно возвращается.

Вот ссылка на Go Playground

1 Ответ

3 голосов
/ 02 августа 2020
  1. Запустите go vet в своем коде, и вы заметите, что он выдает предупреждение loop variable conn captured by func literal, поскольку вы использовали conn вместо переданного параметра, т.е. c. Я исправил это в своем коде. Но это ортогонально вашей проблеме.
  2. Давайте узнаем, как работает 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
...