gpt4 book ai didi

go - 如何使阻塞的外部库调用超时?

转载 作者:IT王子 更新时间:2023-10-29 01:41:08 25 4
gpt4 key购买 nike

(我不认为我的问题是此 QA 的重复:go routine blocking the others one,因为我正在运行 Go 1.9,它具有抢占式调度程序,而该问题是针对 Go 1.2 提出的)。

我的 Go 程序调用了一个由另一个 Go-lang 库包装的 C 库,该库发出的阻塞调用可能会持续 60 多秒。我想添加一个超时,以便它在 3 秒内返回:

长 block 的旧代码:

// InvokeSomething is part of a Go wrapper library that calls the C library read_something function. I cannot change this code.

func InvokeSomething() ([]Something, error) {
ret := clib.read_something(&input) // this can block for 60 seconds
if ret.Code > 1 {
return nil, CreateError(ret)
}
return ret.Something, nil
}

// This is my code I can change:

func MyCode() {

something, err := InvokeSomething()
// etc
}

我的代码带有 go-routine、 channel 和超时,基于这个 Go 示例:https://gobyexample.com/timeouts

type somethingResult struct {
Something []Something
Err error
}

func MyCodeWithTimeout() {
ch = make(chan somethingResult, 1);
go func() {
something, err := InvokeSomething() // blocks here for 60 seconds
ret := somethingResult{ something, err }
ch <- ret
}()
select {
case result := <-ch:
// etc
case <-time.After(time.Second *3):
// report timeout
}
}

但是当我运行 MyCodeWithTimeout它仍然需要 60 秒才能执行 case <-time.After(time.Second * 3) block 。

I know that attempting to read from an unbuffered channel with nothing in it会阻塞,但我创建了缓冲大小为 1 的 channel 据我所知,我做得对。我很惊讶 Go 调度程序没有抢占我的 goroutine,或者这是否取决于在 go-lang 代码中执行而不是外部 native 库?

更新:

我读到 Go 调度程序,至少在 2015 年,实际上是“半抢占式”的,它不会抢占“外部代码”中的操作系统线程:https://github.com/golang/go/issues/11462

you can think of the Go scheduler as being partially preemptive. It's by no means fully cooperative, since user code generally has no control over scheduling points, but it's also not able to preempt at arbitrary points

我听说runtime.LockOSThread()可能会有帮助,所以我将函数更改为:

func MyCodeWithTimeout() {
ch = make(chan somethingResult, 1);
defer close(ch)
go func() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
something, err := InvokeSomething() // blocks here for 60 seconds
ret := somethingResult{ something, err }
ch <- ret
}()
select {
case result := <-ch:
// etc
case <-time.After(time.Second *3):
// report timeout
}
}

...但是它根本没有帮助,它仍然阻塞了 60 秒。

最佳答案

您提出的在 MyCodeWithTimeout() 中启动的 goroutine 中进行线程锁定的解决方案不能保证 MyCodeWithTimeout() 将在 3 秒后返回,以及这样做的原因是第一点:不能保证启动的 goroutine 会被调度并到达将线程锁定到 goroutine 的点,其次:因为即使调用外部命令或系统调用并在 3 秒内返回,也不能保证其他运行 MyCodeWithTimeout() 的 goroutine 将被安排接收结果。

而是在 MyCodeWithTimeout() 中执行线程锁定,而不是在它启动的 goroutine 中:

func MyCodeWithTimeout() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

ch = make(chan somethingResult, 1);
defer close(ch)
go func() {
something, err := InvokeSomething() // blocks here for 60 seconds
ret := somethingResult{ something, err }
ch <- ret
}()
select {
case result := <-ch:
// etc
case <-time.After(time.Second *3):
// report timeout
}
}

现在如果 MyCodeWithTimeout() 开始执行,它会将 goroutine 锁定到 OS 线程,您可以确定这个 goroutine 会注意到定时器值上发送的值。

注意:如果您希望它在 3 秒内返回,这会更好,但是这个窗台不能提供保证,因为触发的计时器(在其 channel 上发送一个值)会自行运行goroutine,这个线程锁对那个goroutine的调度没有影响。

如果你想要保证,你不能依赖于其他 goroutines 给出“退出”信号,你只能依赖于运行 MyCodeWithTimeout() 函数的 goroutine 中发生的这种情况(因为自从你做了线程锁定,你可以确定它被安排了)。

提高给定 CPU 核心的 CPU 使用率的“丑陋”解决方案是:

for end := time.Now().Add(time.Second * 3); time.Now().Before(end); {
// Do non-blocking check:
select {
case result := <-ch:
// Process result
default: // Must have default to be non-blocking
}
}

请注意使用 time.Sleep() 的“冲动”在这个循环中会取消保证,因为 time.Sleep() 可能会在其实现中使用 goroutines,当然不能保证在给定的持续时间后准确返回。

另请注意,如果您有 8 个 CPU 内核和 runtime.GOMAXPROCS(0)为你返回 8,而你的 goroutines 仍然“饥饿”,这可能是一个临时解决方案,但你在你的应用程序中使用 Go 的并发原语(或缺乏使用它们)仍然有更严重的问题,你应该调查和“修复”那些。将线程锁定到一个 goroutine 甚至可能会使其他 goroutine 的情况变得更糟。

关于go - 如何使阻塞的外部库调用超时?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48604783/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com