【发布时间】:2018-07-14 06:13:21
【问题描述】:
(我不认为我的问题与此 QA 重复:go routine blocking the others one,因为我正在运行具有抢占式调度程序的 Go 1.9,而该问题是针对 Go 1.2 提出的)。
我的 Go 程序调用了一个由另一个 Go-lang 库包装的 C 库,该库进行了一个持续超过 60 秒的阻塞调用。我想添加一个超时,让它在 3 秒内返回:
带有长块的旧代码:
// 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、通道和超时,基于这个 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) 块。
I know that attempting to read from an unbuffered channel with nothing in it 会阻塞,但我创建了缓冲大小为1 的通道,据我所知,我做得正确。我很惊讶 Go 调度程序没有抢占我的 goroutine,或者这是否取决于执行是在 go-lang 代码中而不是外部本机库中?
更新:
我了解到,至少在 2015 年,Go 调度程序实际上是“半抢占式”的,它不会抢占“外部代码”中的操作系统线程:https://github.com/golang/go/issues/11462
您可以将 Go 调度程序视为部分抢占式。它绝不是完全协作的,因为用户代码通常无法控制调度点,但它也不能在任意点抢占
听说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 秒。
【问题讨论】:
-
“不重复”代码仍然阻塞在 Playground 中,即 Go 1.9。我怀疑这毕竟是同一个原因。
-
你在设置 GOMAXPROCS 吗?
-
@superfell 项目中的其他代码设置
GOMAXPROCS(runtime.NumCPU()),即我机器上的8。所以它应该仍然有效...... -
在
MyCodeWithTimeout()中进行线程锁定,而不是在它启动的goroutine中。
标签: go concurrency timeout goroutine