如何确保在Go中函数执行一定的时间?

huangapple go评论93阅读模式
英文:

How can I ensure that a function takes a certain amount of time in Go?

问题

我正在为Go中的SQRL客户端实现EnScrypt。该函数需要在使用了最少的CPU时间后才能停止运行。我的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,因为它们测量的是墙钟时间(受系统上可能运行的其他所有内容的影响)。我需要实际使用的CPU时间,最好是由函数本身使用的时间,或者至少是由线程或进程使用的时间。

在Go中,process_time的等效函数是什么?

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

英文:

I am implementing EnScrypt for an SQRL client in Go. The function needs to run until it has used a minimum amount of CPU time. My Python code looks like this:

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

Converting this to Go is pretty simple except for the process_time function.
I can't use time.Time / Timer because those measure wall-clock time (which is affected by everything else that may be running on the system). I need the CPU time actually used, ideally by the function, or at least by the thread or process it is running in.

What is the Go equivalent of process_time?

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

答案1

得分: 2

你可以使用runtime.LockOSThread()将调用的goroutine与其当前的操作系统线程绑定。这将确保没有其他goroutine被调度到该线程上,因此你的goroutine将会运行而不会被中断或暂停。当线程被锁定时,没有其他goroutine会干扰。

在此之后,你只需要一个循环,直到给定的秒数过去为止。你必须调用runtime.UnlockOSThread()来“释放”线程,并使其可供其他goroutine执行,最好使用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

**注意:**请注意,上述函数中的循环将无休止地使用CPU,因为它们没有休眠或阻塞IO操作,它们只是查询当前系统时间并将其与截止时间进行比较。

如果攻击者通过多个并发尝试增加系统负载会怎么样?

Go运行时限制了可以同时执行goroutine的系统线程数量。这由runtime.GOMAXPROCS()控制,因此这已经是一个限制。它默认为可用CPU核心的数量,你可以随时更改它。然而,这也会造成瓶颈,因为通过使用runtime.LockOSThread(),如果在任何给定时间,锁定的线程数等于GOMAXPROCS,那么将会阻塞其他goroutine的执行,直到有一个线程被解锁。

参考相关问题:

https://stackoverflow.com/questions/39245660/number-of-threads-used-by-go-runtime

https://stackoverflow.com/questions/28186361/why-does-it-not-create-many-threads-when-many-goroutines-are-blocked-in-writing/28186656#28186656

英文:

You may use runtime.LockOSThread() to wire the calling goroutine to its current OS thread. This will ensure that no other goroutines will be scheduled to this thread, so your goroutine will run and not get interrupted or put on hold. No other goroutines will interfere when thread is locked.

After this, you just need a loop until the given seconds have passed. You must call runtime.UnlockOSThread() to "release" the thread and make it available for other goroutines for execution, best done as a defer statement.

See this example:

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

To make it wait for 2 seconds, it could look like this:

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

fmt.Println(&quot;Verify:&quot;, time.Now().Sub(start))

This prints for example:

Verify: 2.0004556s

Of course you can specify less than a second too, e.g. to wait for 100 ms:

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

Output:

Verify: 100.1278ms

You may use a different version of this function if that suits you better, one that takes the amount of time to "wait" as a value of time.Duration:

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

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

Using this:

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

Output:

Verify: 200.1546ms

Note: Note that the loops in the above functions will use CPU relentlessly as there is no sleep or blocking IO in them, they will just query the current system time and compare it to the deadline.

What if the attacker increases system load by multiple concurrent attempts?

The Go runtime limits the system threads that can simultaneously execute goroutines. This is controlled by runtime.GOMAXPROCS(), so this is already a limitation. It defaults to the number of available CPU cores, and you can change it anytime. This also poses a bottleneck though, as by using runtime.LockOSThread(), if the number of locked threads equals to GOMAXPROCS at any given time, that would block execution of other goroutines until a thread is unlocked.

See related questions:

https://stackoverflow.com/questions/39245660/number-of-threads-used-by-go-runtime

https://stackoverflow.com/questions/28186361/why-does-it-not-create-many-threads-when-many-goroutines-are-blocked-in-writing/28186656#28186656

huangapple
  • 本文由 发表于 2016年10月16日 03:21:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/40063269.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定