Бесконечный цикл с vs без time.Sleep ()

У меня есть горутина, которая бесконечно проигрывает звук play(). Чтобы сохранить play() в живых, у меня есть вызывающая функция, которая после этого запускает бесконечный цикл for.

Неожиданно то, что цикл barebones, похоже, не позволяет функции работать бесконечно, и я не понимаю, почему. Однако, если я добавлю простой time.Sleep(time.Second) в тело цикла for, он будет работать бесконечно. Есть идеи, почему?

Чтобы визуализировать:

func PlaysForAFewSeconds() {
    go play()
    for {
    }
}

^ играет несколько секунд, но никогда не прерывается

func PlaysForever() {
    go play()
    for {
        time.Sleep(time.Second)
    }
}

^ играет вечно.

Я предполагаю, что это как-то связано с реализацией play(), но я надеюсь, что это достаточно распространенная проблема, чтобы кто-то распознал этот симптом. Спасибо.


person Lucian Thorr    schedule 25.04.2019    source источник
comment
Чтобы сохранить работоспособность play (), у меня есть вызывающая функция, которая после этого запускает бесконечный цикл for. --- это совершенно неправильно. После того, как вы запустили горутину, она не имеет связи с кодом, который ее вызвал. Наличие какого-либо цикла практически не имеет смысла. Если play() блокирует, просто запустите его без горутин.   -  person zerkms    schedule 26.04.2019
comment
Я согласен с тем, что это плохой код, но он не дает никакого представления о том, почему / как горутина будет вести себя по-разному в зависимости от тела цикла for.   -  person Lucian Thorr    schedule 26.04.2019
comment
Сколько процессоров доступно процессу? Какую версию go вы используете? Вы где-нибудь устанавливаете runtime.GOMAXPROCS вручную?   -  person zerkms    schedule 26.04.2019
comment
4 процессора, не возиться с runtime.   -  person Lucian Thorr    schedule 26.04.2019
comment
Это действительно должно быть так. Даже установка fmt.Println() внутри цикла позволяет ему воспроизводиться непрерывно. Я просто почесал в затылке, почему это не сработает. Спасибо. @zerkms И не волнуйтесь, я не буду использовать приведенный выше код :)   -  person Lucian Thorr    schedule 26.04.2019
comment
@LucianThorr fmt.Println (если вы не измените средство записи по умолчанию) вызывает системный вызов, который является безопасной точкой, поэтому, вероятно, это как-то связано с тем, что планировщик не может работать должным образом в этих обстоятельствах.   -  person zerkms    schedule 26.04.2019
comment
Цикл занятости всегда является ошибкой программирования. Даже если планировщик может его прервать, это бесполезно и расходует ресурсы процессора. Просто не делай этого.   -  person JimB    schedule 26.04.2019
comment
@JimB, но если OP имеет 4 процессора, доступных для процесса, имеет ли это значение? Почему он все еще не работает даже с занятым циклом?   -  person zerkms    schedule 26.04.2019
comment
в стороне, мы можем согласиться, что for {} - это плохо. Если вы хотите когда-либо заблокировать подпрограмму на неопределенный срок и не создавать замкнутый цикл, расходующий ресурсы ЦП, используйте вместо этого select {}.   -  person colm.anseo    schedule 26.04.2019
comment
См. stackoverflow.com/questions/33524477/. В текущей реализации это не позволяет планировщику разрешить gc выполнить краткую остановку фазы world. Ваши пользовательские горутины - не единственные, которые нужно запускать. В общем, горутины не являются потоками и планируются совместно.   -  person JimB    schedule 26.04.2019


Ответы (1)


Сборка, которую генерирует for { }, - это jmp self, где self - расположение инструкции jmp. Другими словами, ЦП будет продолжать выполнять jmp инструкции так быстро, как только сможет. ЦП может выполнять n инструкций в секунду, и не имеет значения, бесполезная это jmp или действительно полезная инструкция.

Это известно как «занятое ожидание» или «спин-блокировка», и такое поведение не характерно для Go. Большинство (все?) Языков программирования ведут себя так.

У таких петель есть несколько применений, но в Go их часто можно заменить каналами:

// Simulate a function that takes 1s to complete.
func play(ch chan struct{}) {
    fmt.Println("play")
    time.Sleep(1 * time.Second)
    ch <- struct{}{}
}

func PlaysForAFewSeconds() {
    wait := make(chan struct{})
    go play(wait)
    <-wait
}

func PlaysForever() {
    wait := make(chan struct{})
    for {
        go play(wait)
        <-wait
    }
}

Чтение из канала (<-wait) блокируется и не использует процессор. Я использовал пустую анонимную структуру, которая выглядит немного некрасиво, так как не выделяет никакой памяти.

person Martin Tournoij    schedule 26.04.2019
comment
Я считаю, что первоначальный вопрос был о том, почему именно это происходит. - person zerkms; 26.04.2019
comment
Однако полезное ожидание занятости или блокировка вращения на самом деле проверяет какое-то условие. На самом деле нет смысла использовать пустой jmp self. - person JimB; 26.04.2019