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 的其余部分的任何解决方案感兴趣,但我希望避免任何需要我围绕 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