Как запустить x секунд в обработчике http - PullRequest
0 голосов
/ 01 ноября 2018

Я хочу запустить свою функцию InsertRecords в течение 30 секунд и проверить, сколько записей я могу вставить за данное время.

Как я могу остановить обработку InsertRecords через x секунд, а затем вернуть результат из моего обработчика?

func benchmarkHandler(w http.ResponseWriter, r *http.Request) {

    counter := InsertRecords()

    w.WriteHeader(200)
    io.WriteString(w, fmt.Sprintf("counter is %d", counter))    
}

func InsertRecords() int {
  counter := 0
  // db code goes here
  return counter
}

Ответы [ 2 ]

0 голосов
/ 02 ноября 2018

Как указал 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: Несколько более сложное наблюдение состоит в том, что мой пример не пытается синхронизировать время запуска между таймером и программой. Казалось, немного педантично, чтобы решить эту проблему здесь. Однако вы можете легко синхронизировать два потока, создав канал, который блокирует основной поток, пока программа не закроет его непосредственно перед началом цикла.

0 голосов
/ 01 ноября 2018

Отмены и тайм-ауты часто выполняются с context.Context.

Хотя этот простой пример может быть выполнен только с одним каналом, использование здесь контекста делает его более гибким и может также учитывать отключение клиента.

func benchmarkHandler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
    defer cancel()

    counter := InsertRecords(ctx)

    w.WriteHeader(200)
    io.WriteString(w, fmt.Sprintf("counter is %d", counter))
}

func InsertRecords(ctx context.Context) int {
    counter := 0
    done := ctx.Done()
    for {
        select {
        case <-done:
            return counter
        default:
        }

        // db code goes here
        counter++
    }
    return counter
}

Это будет выполняться не менее 30 секунд, возвращая количество полных итераций базы данных. Если вы хотите быть уверены, что обработчик всегда возвращается сразу после 30 секунд, даже если вызов БД заблокирован, вам нужно вставить код БД в другую процедуру и разрешить ее возврат позже. Кратчайшим примером будет использование шаблона, аналогичного описанному выше, но синхронизация доступа к переменной счетчика, поскольку он может быть записан циклом DB при возврате.

func InsertRecords(ctx context.Context) int {
    counter := int64(0)
    done := ctx.Done()

    go func() {
        for {
            select {
            case <-done:
                return
            default:
            }

            // db code goes here
            atomic.AddInt64(&counter, 1)
        }
    }()

    <-done
    return int(atomic.LoadInt64(&counter))
}

См. Ответ @ JoshuaKolden для примера с продюсером и тайм-аутом, который также можно объединить с существующим контекстом запроса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...