Блокировка Голанга и блокирование

Я несколько смущен тем, как Go обрабатывает не блокирующий IO. API в основном выглядит синхронно мне, и при просмотре презентаций на Go его не редкость слышать такие комментарии, как "и блоки вызовов",

Is Go использует блокировку ввода-вывода при чтении из файлов или сети? Или есть какая-то магия, которая переписывает код при использовании изнутри Go Routine?

Исходя из фона С#, это кажется очень неинтуитивным, в С# у нас есть ключевое слово await при потреблении асинхронных API. Что четко сообщает, что API может дать текущий поток и продолжить позже в продолжении.

Так TL;DR; Will Go блокирует текущий поток при выполнении IO внутри процедуры Go или будет ли он преобразован в С#, например async, ожидающий конечный автомат с продолжением?

Ответ 1

Go имеет планировщик, который позволяет писать синхронный код и переключать контекст самостоятельно и использует async IO под капотом. Поэтому, если вы используете несколько goroutines, они могут работать на одном системном потоке, а когда ваш код блокируется из представления goroutine, он не блокирует. Это не волшебство, но да, это маскирует все это от вас.

Планировщик будет выделять системные потоки, когда они понадобятся, и во время операций, которые действительно блокируются (я думаю, что файл IO блокирует, например, или вызывает код C). Но если вы делаете простой HTTP-сервер, вы можете иметь тысячи и тысячи goroutine, используя на самом деле несколько "реальных потоков".

Вы можете больше узнать о внутренней работе Go here:

https://morsmachine.dk/go-scheduler

Ответ 2

Сначала вы должны прочитать ответ @Not_a_Golfer и ссылку, которую он предоставил, чтобы понять, как планируются голосовые. Мой ответ больше похож на погружение с погружением в сетевое IO. Я предполагаю, что вы понимаете, как Go достигает совместной многозадачности.

Go может и использует только блокировку вызовов, потому что все работает в goroutines, и они не являются реальными потоками ОС. Это зеленые нити. Таким образом, вы можете заставить многих из них блокировать вызовы IO, и они не будут есть всю вашу память и процессор, как потоки ОС.

Файл IO - это только системные вызовы. Not_a_Golfer уже освещал это. Go будет использовать реальный поток ОС для ожидания в syscall и разблокирует goroutine, когда он вернется. Здесь вы можете увидеть реализацию read файла для Unix.

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

Например, когда вы вызываете read в TCP-сокете goroutine, сначала будет пытаться читать с помощью syscall. Если ничего не будет сделано, он будет блокироваться и ждать его возобновления. Блокируя здесь, я имею в виду паркинг, который помещает горутин в очередь, где он ждет возобновления. То, что "заблокированный" goroutine дает выполнение другим goroutines при использовании сетевого ввода-вывода.

func (fd *netFD) Read(p []byte) (n int, err error) {
    if err := fd.readLock(); err != nil {
        return 0, err
    }
    defer fd.readUnlock()
    if err := fd.pd.PrepareRead(); err != nil {
        return 0, err
    }
    for {
        n, err = syscall.Read(fd.sysfd, p)
        if err != nil {
            n = 0
            if err == syscall.EAGAIN {
                if err = fd.pd.WaitRead(); err == nil {
                    continue
                }
            }
        }
        err = fd.eofError(n, err)
        break
    }
    if _, ok := err.(syscall.Errno); ok {
        err = os.NewSyscallError("read", err)
    }
    return
}

https://golang.org/src/net/fd_unix.go?s=#L237

Когда данные поступают, сетевой опросчик возвратит goroutines, который должен быть возобновлен. Вы можете увидеть здесь findrunnable функцию, которая ищет goroutines, которые могут быть запущены. Он вызывает функцию netpoll которая вернет goroutines, которые могут быть возобновлены. Вы можете найти kqueue реализации netpoll здесь.

Что касается async/wait в С#. асинхронная сеть IO также будет использовать асинхронные API (порты завершения ввода-вывода в Windows). Когда что-то придет, OS выполнит обратный вызов на одном из потоков потока завершения потока threadpool, который поместит продолжение в текущий SynchronizationContext. В некотором смысле, есть некоторые общие черты (парковка/разблокировка выглядит как вызов продолжения, но на гораздо более низком уровне), но эти модели очень разные, не говоря уже о реализации. Goroutines по умолчанию не привязаны к конкретному потоку ОС, их можно возобновить на любом из них, это не имеет значения. Нет никаких потоков пользовательского интерфейса. Async/await специально созданы для возобновления работы в одном и том же потоке ОС с помощью SynchronizationContext. И поскольку нет зеленых потоков или отдельного планировщика async/await, необходимо разделить свою функцию на несколько обратных вызовов, которые выполняются в SynchronizationContext который в основном представляет собой бесконечный цикл, который проверяет очередь обратных вызовов, которые должны выполняться. Вы можете даже реализовать его самостоятельно, это очень легко.

Ответ 3

Go блокирует текущий goroutine при выполнении IO или syscalls, но когда это произойдет, другой горутин может запускаться вместо заблокированного goroutine. Старые версии Go допускают только одно запущенное goroutine за раз, но с 1,5, это число изменилось на количество доступных ядер процессора. (runtime.GOMAXPROCS)

Вам не нужно беспокоиться о блокировке в Go. Например, стандартный HTTP-сервер библиотеки выполняет функции обработчика в goroutine. Если вы попытаетесь прочитать файл во время обслуживания HTTP-запроса, который будет заблокирован, но если другой запрос будет включен, а первый будет заблокирован, другой горутин будет разрешен для запуска и обслуживания этого запроса. Затем, когда вторая горутин сделана, а первая больше не заблокирована, она будет возобновлена (если GOMAXPROCS> 1, заблокированная горутин может быть возобновлена еще раньше, если есть свободная нить).

Для получения дополнительной информации ознакомьтесь с ними: