Как указал JimB, отмена ограничения времени, затрачиваемого HTTP-запросами, может быть обработана с помощью context.WithTimeout, однако, поскольку вы попросили провести сравнительный анализ, вы можете использовать более прямой метод.
Цель context.Context - позволить множеству событий отмены иметь одинаковый чистый эффект постепенного прекращения выполнения всех последующих задач. В примере JimB возможно, что какой-то другой процесс отменит контекст до истечения 30 секунд, и это желательно с точки зрения использования ресурсов. Например, если соединение прерывается преждевременно, нет смысла больше работать над созданием ответа.
Если бенчмаркинг является вашей целью, вы бы хотели минимизировать влияние лишних событий на тестируемый код. Вот пример того, как это сделать:
func InsertRecords() int {
stop := make(chan struct{})
defer close(stop)
countChan := make(chan int)
go func() {
defer close(countChan)
for {
// db code goes here
select {
case countChan <- 1:
case <-stop:
return
}
}
}()
var counter int
timeoutCh := time.After(30 * time.Second)
for {
select {
case n := <-countChan:
counter += n
case <-timeoutCh:
return counter
}
}
}
По сути, мы создаем бесконечный цикл над дискретными операциями БД, и, считая итерации в цикле, мы останавливаемся при срабатывании time.After
.
Проблема в примере JimB заключается в том, что, несмотря на проверку ctx.Done () в цикле, цикл все равно может блокироваться, если блокируется "код db". Это связано с тем, что ctx.Done () оценивается только в строке с блоком «db code».
Чтобы избежать этой проблемы, мы разделяем функцию синхронизации и цикл тестирования, чтобы ничто не могло помешать нам получить событие тайм-аута, когда оно происходит. Как только истечет время ожидания, мы немедленно вернем результат счетчика. «Код БД» может все еще находиться в середине выполнения, но InsertRecords завершит работу и все равно выдаст свои результаты.
Если "код БД" находится в середине исполнения при выходе из InsertRecords, программа будет оставлена работающей, поэтому для ее очистки мы defer close(stop)
, так что при выходе из функции мы обязательно дадим сигнал о выходе из программы на следующей итерации. Когда программа закрывается, она очищает канал, который использовалась для отправки счета.
В качестве общего шаблона выше приведен пример того, как вы можете получить точную синхронизацию в Go без учета фактического времени выполнения синхронизируемого кода.
sidenote: Несколько более сложное наблюдение состоит в том, что мой пример не пытается синхронизировать время запуска между таймером и программой. Казалось, немного педантично, чтобы решить эту проблему здесь. Однако вы можете легко синхронизировать два потока, создав канал, который блокирует основной поток, пока программа не закроет его непосредственно перед началом цикла.