Как устроена функция time.Sleep() в Golang
Содержание
Вам наверняка не раз доводилось писать такой код:
func main() {
// ...
time.Sleep(4 * time.Second)
// ...
}
Он заставляет программу (горутину) подождать указанное количество секунд. Но что происходит внутри функции time.Sleep()? Давайте разберемся.
Как работает time.Sleep()
Давайте сразу посмотрим реализацию (go1.19):
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
if ns <= 0 {
return
}
gp := getg()
t := gp.timer
if t == nil {
t = new(timer)
gp.timer = t
}
t.f = goroutineReady
t.arg = gp
t.nextwhen = nanotime() + ns
if t.nextwhen < 0 { // check for overflow.
t.nextwhen = maxWhen
}
gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
}
Разберёмся, что здесь происходит:
- Первым делом мы проверяем, что
nsбольше нуля. Если меньше, то просто возвращаемся.ns- это количество наносекунд, которые мы хотим подождать. - Далее мы получаем текущую горутину с помощью
getg(). Эта функция возвращает указатель на структуруg, которая описывает горутину - Получаем таймер из горутины с помощью
gp.timer. Если таймера нет, то мы создаем новый. Это нужно для того, чтобы не создавать новый таймер на каждый вызовtime.Sleep(), переиспользуя существующий - это экономит память и время
Таймер - это структура, описывающая событие, которое должно произойти в будущем. В нашем случае такое событие - это готовность горутины - Устанавливаем функцию, которая будет вызвана по истечении таймера и аргументы для этой функции.
ФункцияgoroutineReadyпросто устанавливает флагreadyвtrueдля указанной горутины.gp- это указатель на текущую горутину, который мы получили выше - Устанавливаем время, когда таймер должен сработать: получаем текущее время в наносекундах с помощью
nanotime(), прибавляем к немуnsи сохраняем вt.nextwhen - Если
t.nextwhen < 0, значит произошло переполнение. В этом случае мы устанавливаемt.nextwhenравнымmaxWhen(максимальное значениеint64) - Вызываем
gopark()для ожидания.gopark()- это функция, которая переводит горутину в состояние ожидания. В нашем случае до тех пор, пока не сработает таймер. У неё пять аргументов:- Функция, которая будет вызвана, когда таймер сработает. В нашем случае это
resetForSleep(), которая сбрасывает таймер - Указатель на таймер, который мы создали или получили выше
- Причина, по которой горутина переводится в состояние ожидания. В нашем случае это
waitReasonSleep - Событие, которое будет записано в трассировку. В нашем случае это
traceEvGoSleep - Флаг, который указывает, что горутина должна быть заблокирована
- Функция, которая будет вызвана, когда таймер сработает. В нашем случае это
- Когда таймер сработает, мы вызываем
goroutineReady(), которая устанавливает флаг горутиныreadyвtrue.
Теперь, когда состояние горутины ready, ей осталось лишь дождаться, когда планировщик её снова запустит.
Николай Тузов