Подтвердить что ты не робот

Почему эта блокировка кода Go?

Я написал следующую программу:

package main

import (
    "fmt"
)

func processevents(list chan func()) {
    for {
        //a := <-list
        //a()
    }
}

func test() {
    fmt.Println("Ho!")
}

func main() {

    eventlist := make(chan func(), 100)

    go processevents(eventlist)

    for {
        eventlist <- test
        fmt.Println("Hey!")
    }
}

Так как список событий канала является буферизованным каналом, я думаю, что я должен получить ровно 100 раз выход "Эй!", но он отображается только один раз. Где моя ошибка?

4b9b3361

Ответ 1

Обновление (Go версии 1.2 +)

Начиная с Go 1.2, планировщик работает по принципу упреждающей многозадачности. Это означает, что проблема в исходном вопросе (и нижеприведенное решение) больше не актуальна.

Из примечаний Go 1.2

Предотвращение в планировщике

В предыдущих выпусках, горутин, который зацикливался навсегда, мог бы изгонять других гортанов в том же потоке, серьезная проблема, когда GOMAXPROCS предоставил только один пользовательский поток. В Go > 1.2 это частично исправлено: планировщик иногда ссылается на вход в функцию. Это означает, что любой цикл, который включает (не вложенную) функцию вызов может быть предварительно упущен, что позволяет другим гортанам работать в одном потоке.

Короткий ответ

Он не блокирует записи. Он застрял в бесконечном цикле processevents. Этот цикл никогда не уступает планировщику, заставляя все goroutines блокироваться неограниченно.

Если вы закомментируете вызов processevents, вы получите результаты, как ожидалось, вплоть до 100-й записи. В этот момент программа панически, потому что никто не читает с канала.

Другим решением является вызов runtime.Gosched() в цикле.

Длинный ответ

С Go1.0.2 Планировщик Go работает по принципу Совместная многозадачность. Это означает, что он выделяет процессорное время для различных goroutines, работающих в заданном потоке операционной системы, поскольку эти подпрограммы взаимодействуют с планировщиком в определенных условиях. Эти "взаимодействия" возникают, когда определенные типы кода выполняются в goroutine. В данном случае это предполагает выполнение каких-либо операций ввода-вывода, системных вызовов или распределения памяти (в определенных условиях).

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

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

for {
    runtime.Gosched()
}

Значение GOMAXPROCS для значения > 1 было упомянуто как решение. Хотя это избавит вас от непосредственной проблемы, которую вы заметили, она эффективно переместит проблему в другой поток ОС, если планировщик решит переместить цикл goroutine в свой собственный поток ОС. Это не гарантирует, если вы не назовете runtime.LockOSThread() в начале функции processevents. Даже тогда я все равно не буду полагаться на этот подход, чтобы быть хорошим решением. Просто вызывая runtime.Gosched() в самом цикле, решит все проблемы, независимо от того, какой поток ОС работает в goroutine.

Ответ 2

Вот еще одно решение - используйте range для чтения с канала. Этот код будет корректно работать с планировщиком и также корректно завершиться, когда канал будет закрыт.

func processevents(list chan func()) {
    for a := range list{
        a()
    }
}

Ответ 3

Хорошие новости, так как Go 1.2 (декабрь 2013), оригинальная программа теперь работает так, как ожидалось. Вы можете попробовать его на игровой площадке.

Это объясняется в заметках о выпуске Go 1.2, раздел "Pre-emption in scheduler":

В предыдущих выпусках горутин, который зацикливал навсегда, мог голодать из других горутов в той же теме, серьезная проблема, когда GOMAXPROCS предоставил только один пользовательский поток. В Go 1.2 это частично адрес: планировщик иногда вызывается при входе в функция.