cgo 与使用线程本地存储的 C 库交互
cgo Interacting with C Library that uses Thread Local Storage
我正在用 cgo 包装一个 C 库以供普通 Go 代码使用。
我的问题是我想将错误字符串传播到 Go API,但是有问题的 C 库通过线程本地存储提供错误字符串;有一个全局 get_error()
调用 returns 一个指向线程本地字符数据的指针。
我最初的计划是通过 cgo 调用 C,检查调用是否返回错误,如果是,则使用 C.GoString
包装错误字符串,将其从原始字符指针转换为 Go 字符串.它看起来像 C.GoString(C.get_error())
.
我在这里预见的问题是 C 中的 TLS 在本机 OS 线程级别工作,但据我了解,调用 Go 代码将来自潜在的 N 个多路复用的 goroutines 之一在由 Go 调度程序管理的线程池中跨一些底层本机线程。
我害怕的是 运行 我调用 C 例程,然后在 C 例程之后 returns,但在我复制错误字符串之前,Go 调度程序决定将当前的 goroutine 换成另一个。当原来的 goroutine 被换回时,据我所知,它可能位于不同的本机线程上,但即使它被换回同一个线程,任何 运行 在其间的 goroutines 都可以'我们更改了 TLS 的状态,导致我为不相关的调用加载错误字符串。
我的问题是:
- 这是一个合理的担忧吗?我是否误解了有关 go 调度程序的某些内容,或者它与 cgo 交互的方式,这会导致这不是问题?
- 如果这是一个合理的担忧,我该如何解决?
- cgo 以某种方式设法将 errno 值传播回调用 Go 代码,这些代码也存储在 TLS 中,这让我认为必须有一种安全的方法来做到这一点。
- 我想不出 C 代码本身可以被 go 调度程序抢占的方法,所以我应该引入一个包装 C 函数并让 IT 进行必要的调用,然后在返回之前有条件地复制错误字符串回到戈兰岛?
我对允许我将错误字符串传播到 Go 的其余部分的任何解决方案感兴趣,但我希望避免任何需要我围绕 TLS 序列化访问的解决方案,因为添加仅仅为了获取错误字符串而锁定对我来说似乎非常不幸。
提前致谢!
What I'm afraid of is running into a situation where I call into the C routine, then after the C routine returns, but before I copy the error string, the Go scheduler decides to swap the current goroutine out for another one. ...
Is this a reasonable concern?
是的。 cgo“调用 C 代码”包装器在每次调用期间锁定到一个 POSIX / OS 线程,但它们锁定的线程并非始终固定;事实上,它确实 bop around,随着时间的推移,只要你的 goroutines 正常运行,它就会对多个不同的线程起作用。 (由于 Go 在当前实现中是协同调度的,在某些情况下,您可以注意不要做任何可能让您切换底层 OS 线程的事情,但这可能不是一个好计划。)
你可以在这里使用runtime.LockOSThread
,但我认为最好的方案是:
how can I work around it?
抓取错误before Go 恢复其正常的调度算法(即在从 C/POSIX 线程解锁 goroutine 之前)
cgo somehow manages to propagate errno values ...
它在从 POSIX 线程解锁 goroutine 之前获取 errno 值。
My original plan was to call into C via cgo, check if the call returned an error, and if so, wrap the error string using C.GoString
to convert it from a raw character pointer into a Go string. It'd look something like C.GoString(C.get_error())
.
如果有一个变体接受错误 number(而不是从 TLS 变量中取出它),该计划应该仍然有效:只需确保您的C 例程同时提供 return 值和错误号。
如果没有,请按照您的建议编写您自己的 C 包装器:
ftype wrapper_for_realfunc(char **errp, arg1type arg1, arg2type arg2) {
ftype ret = realfunc(arg1, arg2);
if IS_ERROR(ret) {
*errp = get_error();
} else {
*errp = NULL;
}
return ret;
}
现在你的 Go 包装器简单地调用包装器,它用一个额外的 *C.char
参数填充一个指向 C 内存的指针,如果没有错误则将其设置为 nil,并将其设置为你可以在其上的东西出错可以用C.GoString
如果由于某种原因这不可行,请考虑使用 runtime.LockOSThread
及其对应物 runtime.UnlockOSThread
。
我正在用 cgo 包装一个 C 库以供普通 Go 代码使用。
我的问题是我想将错误字符串传播到 Go API,但是有问题的 C 库通过线程本地存储提供错误字符串;有一个全局 get_error()
调用 returns 一个指向线程本地字符数据的指针。
我最初的计划是通过 cgo 调用 C,检查调用是否返回错误,如果是,则使用 C.GoString
包装错误字符串,将其从原始字符指针转换为 Go 字符串.它看起来像 C.GoString(C.get_error())
.
我在这里预见的问题是 C 中的 TLS 在本机 OS 线程级别工作,但据我了解,调用 Go 代码将来自潜在的 N 个多路复用的 goroutines 之一在由 Go 调度程序管理的线程池中跨一些底层本机线程。
我害怕的是 运行 我调用 C 例程,然后在 C 例程之后 returns,但在我复制错误字符串之前,Go 调度程序决定将当前的 goroutine 换成另一个。当原来的 goroutine 被换回时,据我所知,它可能位于不同的本机线程上,但即使它被换回同一个线程,任何 运行 在其间的 goroutines 都可以'我们更改了 TLS 的状态,导致我为不相关的调用加载错误字符串。
我的问题是:
- 这是一个合理的担忧吗?我是否误解了有关 go 调度程序的某些内容,或者它与 cgo 交互的方式,这会导致这不是问题?
- 如果这是一个合理的担忧,我该如何解决?
- cgo 以某种方式设法将 errno 值传播回调用 Go 代码,这些代码也存储在 TLS 中,这让我认为必须有一种安全的方法来做到这一点。
- 我想不出 C 代码本身可以被 go 调度程序抢占的方法,所以我应该引入一个包装 C 函数并让 IT 进行必要的调用,然后在返回之前有条件地复制错误字符串回到戈兰岛?
我对允许我将错误字符串传播到 Go 的其余部分的任何解决方案感兴趣,但我希望避免任何需要我围绕 TLS 序列化访问的解决方案,因为添加仅仅为了获取错误字符串而锁定对我来说似乎非常不幸。
提前致谢!
What I'm afraid of is running into a situation where I call into the C routine, then after the C routine returns, but before I copy the error string, the Go scheduler decides to swap the current goroutine out for another one. ...
Is this a reasonable concern?
是的。 cgo“调用 C 代码”包装器在每次调用期间锁定到一个 POSIX / OS 线程,但它们锁定的线程并非始终固定;事实上,它确实 bop around,随着时间的推移,只要你的 goroutines 正常运行,它就会对多个不同的线程起作用。 (由于 Go 在当前实现中是协同调度的,在某些情况下,您可以注意不要做任何可能让您切换底层 OS 线程的事情,但这可能不是一个好计划。)
你可以在这里使用runtime.LockOSThread
,但我认为最好的方案是:
how can I work around it?
抓取错误before Go 恢复其正常的调度算法(即在从 C/POSIX 线程解锁 goroutine 之前)
cgo somehow manages to propagate errno values ...
它在从 POSIX 线程解锁 goroutine 之前获取 errno 值。
My original plan was to call into C via cgo, check if the call returned an error, and if so, wrap the error string using
C.GoString
to convert it from a raw character pointer into a Go string. It'd look something likeC.GoString(C.get_error())
.
如果有一个变体接受错误 number(而不是从 TLS 变量中取出它),该计划应该仍然有效:只需确保您的C 例程同时提供 return 值和错误号。
如果没有,请按照您的建议编写您自己的 C 包装器:
ftype wrapper_for_realfunc(char **errp, arg1type arg1, arg2type arg2) {
ftype ret = realfunc(arg1, arg2);
if IS_ERROR(ret) {
*errp = get_error();
} else {
*errp = NULL;
}
return ret;
}
现在你的 Go 包装器简单地调用包装器,它用一个额外的 *C.char
参数填充一个指向 C 内存的指针,如果没有错误则将其设置为 nil,并将其设置为你可以在其上的东西出错可以用C.GoString
如果由于某种原因这不可行,请考虑使用 runtime.LockOSThread
及其对应物 runtime.UnlockOSThread
。