cgo 与使用线程本地存储的 C 库交互

我正在用 cgo 包装一个 C 库以供普通 Go 代码使用。

我的问题是我想将错误字符串传播到 Go API,但是有问题的 C 库通过线程本地存储提供错误字符串;有一个全局get_error()调用返回指向线程本地字符数据的指针。

我最初的计划是通过 cgo 调用 C,检查调用是否返回错误,如果是,则将错误字符串包装起来C.GoString,将其从原始字符指针转换为 Go 字符串。它看起来像C.GoString(C.get_error()).

我在这里预见到的问题是 C 中的 TLS 在本机 OS 线程级别上工作,但据我了解,调用 Go 代码将来自潜在的 N 个 goroutine 之一,这些 goroutine 在某个数量的底层本地线程中多路复用由 Go 调度程序管理的线程池。

我害怕遇到这样一种情况:我调用了 C 例程,然后在 C 例程返回之后,但在我复制错误字符串之前,Go 调度程序决定将当前的 goroutine 换成另一个。当原始的 goroutine 被换回时,据我所知,它可能位于不同的本地线程上,但即使它被换回同一个线程,在此期间运行的任何 goroutine 都可能改变了状态的 TLS,导致我为不相关的调用加载错误字符串。

我的问题是:

  • 这是一个合理的担忧吗?我是否误解了 go 调度程序,或者它与 cgo 交互的方式,这会导致这不是问题?

  • 如果这是一个合理的问题,我该如何解决?

    • cgo 设法将 errno 值传播回调用 Go 代码,这些代码也存储在 TLS 中,这让我认为必须有一种安全的方法来做到这一点。

    • 我想不出 C 代码本身可以被 go 调度程序抢占的方式,所以我应该引入一个包装 C 函数并让 IT 进行必要的调用,然后在返回 goland 之前有条件地复制错误字符串?

我对任何允许我将错误字符串传播到 Go 的其余部分的解决方案感兴趣,但我希望避免任何需要我序列化围绕 TLS 的访问的解决方案,因为添加一个锁只是为了抓取一个错误字符串对我来说似乎非常不幸。

提前致谢!


神不在的星期二
浏览 182回答 1
1回答

拉风的咖菲猫

害怕遇到这样一种情况:我调用了 C 例程,然后在 C 例程返回之后,但在我复制错误字符串之前,Go 调度程序决定将当前的 goroutine 换成另一个。...这是一个合理的担忧吗?是的。cgo“调用 C 代码”包装器在每次调用期间锁定一个 POSIX / OS 线程,但它们锁定的线程并非一直固定;实际上,只要您的 goroutine 正常运行,它确实会随着时间的推移跳转到多个不同的线程。(由于 Go 在当前实现中是协作调度的,在某些情况下,您可以小心不要做任何可能让您切换底层操作系统线程的事情,但这可能不是一个好计划。)你可以在这里使用runtime.LockOSThread,但我认为最好的计划是:我该如何解决?在Go 恢复其正常的调度算法之前(即,在从 C/POSIX 线程解锁 goroutine 之前)获取错误。cgo 以某种方式设法传播 errno 值...它在从 POSIX 线程解锁 goroutine之前获取 errno 值。我最初的计划是通过 cgo 调用 C,检查调用是否返回错误,如果是,则将错误字符串包装起来C.GoString,将其从原始字符指针转换为 Go 字符串。它看起来像C.GoString(C.get_error()).如果有这样的变体获取错误号(而不是将其从 TLS 变量中取出),那么该计划应该仍然有效:只需确保您的 C 例程提供返回值和错误号即可。如果没有,请按照您的建议编写自己的 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 内存的指针,*C.char如果没有错误,则将其设置为 nil,如果有错误,则将其设置为可以使用的东西C.GoString。如果由于某种原因这不可行,请考虑使用runtime.LockOSThread及其对应的runtime.UnlockOSThread.
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go