Если метод Wait () типа sync.WaitGroup блокируется и, следовательно, не является асинхронным, зачем его использовать? - PullRequest
0 голосов
/ 07 сентября 2018

Я изучал Голанг и видел, насколько хорош его параллелизм с применением модели только для канала сопрограмм через инновационную конструкцию goroutines.

Одна вещь, которая сразу вызывает у меня беспокойство, - это использование метода Wait(), используемого для ожидания завершения нескольких невыполненных подпрограмм, порожденных внутри родительской подпрограммы. Цитировать Golang docs

Ожидание может быть использовано для блокировки, пока все goroutines не закончили

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

Процесс [или поток], который заблокирован, - это процесс, ожидающий какого-либо события, например, когда ресурс станет доступным или завершится операция ввода-вывода.

Другими словами, заблокированный поток будет тратить циклы ЦП, не делая ничего полезного, просто многократно проверяя, может ли текущая запущенная задача прекратить ожидание и продолжить выполнение.

В действительно асинхронном коде, когда сопрограмма сталкивается с ситуацией, когда она не может продолжаться до тех пор, пока не будет получен результат, она должна передать свое выполнение планировщику вместо блокировки, переключив ее состояние от выполняется до ожидание , поэтому планировщик может начать выполнение следующей подпрограммы в очереди из очереди runnable . Состояние ожидания для сопрограммы должно быть изменено с ожидающего на работоспособное только после достижения необходимого результата.

Таким образом, поскольку Wait() блокирует до тех пор, пока x число goroutines не вызовет Done(), подпрограмма, которая вызывает Wait(), всегда будет оставаться либо в рабочем состоянии, либо в рабочем состоянии, тратя циклы ЦП и полагаясь на планировщик, чтобы выгрузить длительно работающая программа только для того, чтобы изменить ее состояние с работающего на работоспособное, вместо того, чтобы изменить его на ожидание, как и должно быть.

Если все это правда, и я понимаю, как Wait() работает правильно, то почему люди не используют встроенные каналы Go для задачи ожидания выполнения подпрограмм? Если я правильно понимаю, отправка в буферизованный канал и чтение из любого канала являются асинхронными операциями, а это означает, что их вызов переведет программу в состояние ожидания, так почему же они не являются предпочтительным методом?

В статье, на которую я ссылался, приводится несколько примеров. Вот что автор называет «старой школой»:

package main

import (
    "fmt"
    "time"
)

func main() {
    messages := make(chan int)
    go func() {
        time.Sleep(time.Second * 3)
        messages <- 1
    }()
    go func() {
        time.Sleep(time.Second * 2)
        messages <- 2
    }()
    go func() {
        time.Sleep(time.Second * 1)
        messages <- 3
    }()
    for i := 0; i < 3; i++ {
        fmt.Println(<-messages)
    }
}

и вот предпочтительный, "канонический" способ:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    messages := make(chan int)
    var wg sync.WaitGroup
    wg.Add(3)
    go func() {
        defer wg.Done()
        time.Sleep(time.Second * 3)
        messages <- 1
    }()
    go func() {
        defer wg.Done()
        time.Sleep(time.Second * 2)
        messages <- 2
    }() 
    go func() {
        defer wg.Done()
        time.Sleep(time.Second * 1)
        messages <- 3
    }()
    wg.Wait()
    for i := range messages {
        fmt.Println(i)
    }
}

Я могу понять, что второе может быть легче для понимания, чем первое, но первое является асинхронным, когда никакие сопрограммы не блокируются, а второе имеет одну сопрограмму, которая блокирует: ту, которая выполняет основную функцию. Здесь является еще одним примером того, что Wait() является общепринятым подходом.

Почему Wait() не считается анти-паттерном сообществом Go, если оно создает неэффективный заблокированный поток? Почему в этой ситуации большинство не предпочитают каналы, поскольку их можно использовать для обеспечения асинхронности всего кода и оптимизации потока?

1 Ответ

0 голосов
/ 07 сентября 2018

Ваше понимание «блокировки» неверно.Блокирующие операции, такие как WaitGroup.Wait() или получение канала (когда нет значения для получения) только блокируют выполнение программы, они не (обязательно) блокируют поток ОС, который используется для выполнения (операторов) программы.

Всякий раз, когда встречается операция блокировки (такая как вышеупомянутая), планировщик распорядка может (и будет) переключаться на другую подпрограмму, которая может продолжать выполняться.Нет (значительных) циклов ЦП, потерянных во время вызова WaitGroup.Wait(), если есть другие программы, которые могут продолжать работать, они будут.

Пожалуйста, проверьте связанный вопрос: Количество потоков, используемых Goвыполнения

...