Golang каналы - как работают

Объясните, пожалуйста, как работает код? и как сделать так, чтобы он работал за 3 секунды?

func worker() chan int {
   ch := make(chan int)

   go func() {
      time.Sleep(3 * time.Second)
      ch <- 42
   }()

   return ch
}

func main() {
   timeStart := time.Now()

   _, _ = <-worker(), <-worker()

   println(int(time.Since(timeStart).Seconds()))
} 

Ответы (2 шт):

Автор решения: KoVadim

Способ 1:

заменить

time.Sleep(3 * time.Second)

на

time.Sleep(1500 * time.Millisecond)

Способ 2:

Заменить

_, _ = <-worker(), <-worker()

на

_ = <-worker()

А как работают каналы, лучше почитать стандартную документацию или статью на хабре

→ Ссылка
Автор решения: Igor Chubin

Ключевая проблема здесь в том, что в этом присвоении каждый из воркеров отрабатывает последовательно (несмотря на их внутреннюю конкурентность), и задержка у вас таким образом суммируется:

_, _ = <-worker(), <-worker()

Один из способов, в дополнение к тем, которые приводит KoVadim, было бы запускать воркеры не последовательно, а параллельно.

Горутины

В простейшем случае, когда воркеры работают как обычные функции и не выполняются в фоне, их можно было бы распареллелить, обернув вызов каждого в горутину:

go func() {
  _ = <-worker()
}()

При этом вам нужно координировать их работу, и ждать пока они оба завершатся, например через WaitGroup или как либо-ещё.

Вот как обычно это делается через WaitGroup (это требует небольшой модификации вашего кода):

    var wg sync.WaitGroup

    for i := 1; i <= 2; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()

То есть, при создании каждого воркера (который запускается на фоне), мы увеличиваем значение счётчика внутри wg.

После того как все воркеры запущены, мы ожидаем их заверешения wg.Wait().

Внутри же воркера вам нужно в таком случае добавить вызов:

  defer wg.Done()

который будет сигнализировать о том, что воркер завершился (и уменьшать значение счётчика WaitGroup)

select

В том случае, если воркеры сами по себе исполняются конкурентно (как раз в нашем случае) и нужно просто вызвать их так, чтобы они не блокировали друг друга, можно воспользоваться select:

    for i := 0; i < 2; i++ {
        select {
        case _ = <-worker():
            fmt.Println("worker finished")
        case _ = <-worker():
            fmt.Println("worker finished")
        }
    }

(идеально было бы чтобы воркеры различались каким-то образом, например, с помощью аргумента, который мы бы ему передавали, но в задаче они никак не различаются — хорошо)

При таком вызове они будут испольняться независимо друг от друга и друг друга блокировать не будут.

→ Ссылка