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 шт):
Способ 1:
заменить
time.Sleep(3 * time.Second)
на
time.Sleep(1500 * time.Millisecond)
Способ 2:
Заменить
_, _ = <-worker(), <-worker()
на
_ = <-worker()
А как работают каналы, лучше почитать стандартную документацию или статью на хабре
Ключевая проблема здесь в том, что в этом присвоении каждый из воркеров отрабатывает последовательно (несмотря на их внутреннюю конкурентность), и задержка у вас таким образом суммируется:
_, _ = <-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")
}
}
(идеально было бы чтобы воркеры различались каким-то образом, например, с помощью аргумента, который мы бы ему передавали, но в задаче они никак не различаются — хорошо)
При таком вызове они будут испольняться независимо друг от друга и друг друга блокировать не будут.