Как я могу гарантировать, что функция занимает определенное количество времени в Go?

Я реализую EnScrypt для клиента SQRL в Go. Функция должна запускаться до тех пор, пока не будет использовано минимальное количество процессорного времени. Мой код Python выглядит так:

def enscrypt_time(salt, password, seconds, n=9, r=256):
    N = 1 << n
    start = time.process_time()
    end = start + seconds
    data = acc = scrypt.hash(password, salt, N, r, 1, 32)
    i = 1
    while time.process_time() < end:
        data = scrypt.hash(password, data, N, r, 1, 32)
        acc = xor_bytes(acc, data)
        i += 1
    return i, time.process_time() - start, acc

Преобразовать это в Go довольно просто, за исключением process_time функция. Я не могу использовать time.Time / Timer потому что они измеряют время настенных часов (на которое влияет все остальное, что может работать в системе). Мне нужно процессорное время, фактически используемое, в идеале функцией, или, по крайней мере, потоком или процессом, в котором оно выполняется.

Что является эквивалентом Go process_time?

https://docs.python.org/3/library/time.html

1 ответ

Вы можете использовать runtime.LockOSThread() подключить вызывающую программу к текущему потоку ОС. Это гарантирует, что никакие другие подпрограммы не будут запланированы в этом потоке, поэтому ваша подпрограмма будет работать и не будет прервана или приостановлена. Никакие другие процедуры не будут мешать, когда поток заблокирован.

После этого вам просто нужен цикл, пока не пройдут заданные секунды. Вы должны позвонить runtime.UnlockOSThread() "освободить" поток и сделать его доступным для выполнения другими процедурами, лучше всего это сделать defer заявление.

Смотрите этот пример:

func runUntil(end time.Time) {
    runtime.LockOSThread()
    defer runtime.UnlockOSThread()
    for time.Now().Before(end) {
    }
}

Чтобы заставить его ждать 2 секунды, это может выглядеть так:

start := time.Now()
end := start.Add(time.Second * 2)
runUntil(end)

fmt.Println("Verify:", time.Now().Sub(start))

Это печатает, например:

Verify: 2.0004556s

Конечно, вы также можете указать менее секунды, например, подождать 100 мс:

start := time.Now()
runUntil(start.Add(time.Millisecond * 100))
fmt.Println("Verify:", time.Now().Sub(start))

Выход:

Verify: 100.1278ms

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

func wait(d time.Duration) {
    runtime.LockOSThread()
    defer runtime.UnlockOSThread()

    for end := time.Now().Add(d); time.Now().Before(end); {
    }
}

Используя это:

start = time.Now()
wait(time.Millisecond * 200)
fmt.Println("Verify:", time.Now().Sub(start))

Выход:

Verify: 200.1546ms

Примечание. Обратите внимание, что циклы в вышеприведенных функциях будут беспрерывно использовать ЦП, поскольку в них нет сна или блокировки ввода-вывода, они просто будут запрашивать текущее системное время и сравнивать его с крайним сроком.

Что делать, если злоумышленник увеличивает нагрузку на систему при нескольких одновременных попытках?

Среда выполнения Go ограничивает системные потоки, которые могут одновременно выполнять программы. Это контролируется runtime.GOMAXPROCS() так что это уже ограничение. По умолчанию это число доступных ядер ЦП, и вы можете изменить его в любое время. Это также создает узкое место, хотя, используя runtime.LockOSThread(), если количество заблокированных потоков равно GOMAXPROCS в любой момент времени это заблокировало бы выполнение других программ, пока поток не будет разблокирован.

Смотрите связанные вопросы:

Количество потоков, используемых средой выполнения Go

Почему это не создает много потоков, когда многие goroutines заблокированы в записи файла в golang?

Другие вопросы по тегам