【问题标题】:Check if function is being called as goroutine or not检查函数是否被称为 goroutine
【发布时间】:2019-06-21 22:00:32
【问题描述】:

有什么方法可以判断一个正在运行的函数是否被调用为 goroutine?

我读过 'go tour' 并且对使用 golang 构建 websocket 服务器很感兴趣,所以我找到了这个教程 https://tutorialedge.net/golang/go-websocket-tutorial/

现在我想知道教程中的 wsEndpoint 函数是否作为 goroutine 调用(例如 go wsEndpoint(...))。

我试图阅读 http 包文档,但没有得到清晰的图片,只是猜测处理程序将被 goroutine 调用。这是真的吗?

【问题讨论】:

  • 是的,HTTP 处理程序在它们自己的 goroutine 中被调用。请注意,every 函数在 goroutine 中运行,甚至是 main。通常无法确定一个函数是否在与另一个函数不同的 goroutine 中运行。存在一些 hack,但它们依赖于运行时的实现细节,因此不可移植或受兼容性保证的约束。
  • 假设我需要从我的函数中访问一些外部数据进行修改,但我不知道该函数是否在另一个 go 例程中被调用。那么如何确定是否需要使用互斥锁来序列化对数据的访问呢?
  • 是否需要同步不是运行时的决定。它是在编写代码和编写您已经知道的代码时制作的。
  • 这就是问题所在,我想我不知道,因为我不知道我的函数将如何从其他包中调用...
  • 这是您的决定;您可以自己解决问题,也可以让用户处理。只需记录您的功能是否可以安全地同时使用。无论这是否常见,您都应该有某种直觉。

标签: go goroutine


【解决方案1】:

每个函数都从一个 goroutine 调用,即使是 main() 函数(称为 main goroutine)。

Go 中的 goroutine 没有身份。哪个 goroutine 调用一个函数并不重要。

回答您的“原始”问题:

有什么方法可以判断一个正在运行的函数是否被调用为 goroutine?

如果我们将 this 定义为使用 go 语句或不使用该语句调用的函数,那么答案是肯定的:我们可以检查。

但在我们这样做之前:我不会将这些信息用于任何事情。不要编写依赖于此的代码,也不要编写依赖于哪个 goroutine 调用函数的代码。如果你需要从多个 goroutines 并发访问一个资源,只需使用适当的同步。

基本上我们可以检查调用堆栈:相互调用的函数列表。如果该函数位于该列表的顶部,则使用 go 调用它(检查答案末尾的注释)。如果调用堆栈中在此之前还有其他函数,那么它是在没有go 的情况下从另一个函数调用的(位于调用堆栈之前的)。

我们可以使用runtime.Callers() 来获取调用goroutine 的堆栈。这就是我们如何检查是否有其他函数调用“我们”:

func f(name string) {
    count := runtime.Callers(3, make([]uintptr, 1))
    if count == 0 {
        fmt.Printf("%q is launched as new\n", name)
    }
}

测试它:

func main() {
    f("normal")
    go f("with-go")

    func() { f("from-anon") }()
    func() { go f("from-anon-with-go") }()

    f2("from-f2")
    go f2("with-go-from-f2")

    f3("from-f3")
    go f3("with-go-from-f3")

    time.Sleep(time.Second)
}

func f2(name string) { f(name) }
func f3(name string) { go f(name) }

这将输出(在Go Playground 上尝试):

"with-go" is launched as new
"from-anon-with-go" is launched as new
"from-f3" is launched as new
"with-go-from-f3" is launched as new

注意:基本上所有调用堆栈的“顶部”都有一个runtime.goexit() 函数,这是运行在goroutine 上的最顶层函数,也是所有goroutine 的“退出”点。这就是我们从堆栈中跳过 3 帧的原因(0. 是 runtime.Callers() 本身,1. 是 f() 函数,最后要跳过的帧是 runtime.goexit())。您可以在此Go Playground 中检查带有函数和文件名+行号的完整调用堆栈。这不会改变这个解决方案的可行性,只是我们必须跳过 3 帧而不是 2 帧来判断 f() 是从另一个函数调用还是使用 go 语句调用。

【讨论】:

  • 如果我提供自己的函数,该函数从某个包(例如 http 包)访问共享数据到另一个函数,我应该使用互斥锁来同步对数据的访问吗?总是?
  • @Sergey 是的,HTTP 处理程序是从它们自己的 goroutine 中调用的,因此如果它们访问处理程序函数之外的数据(包括写入),则需要同步。详情见Process Management for the Go Webserver
  • 好吧,也许 HTTP 处理程序不再是一个很好的例子了。如果你开始使用一些新的包,但你不知道包的内部结构,你会怎么做?在您的函数中实现序列化访问或查看新包的源代码以了解其中发生了什么?
  • @Sergey 查看它的文档。如果它记录了并发使用是安全的,那很好。如果不是,请不要做出这样的假设。
猜你喜欢
  • 2019-02-11
  • 2010-09-26
  • 2018-03-19
  • 1970-01-01
  • 2021-10-05
  • 1970-01-01
  • 2011-09-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多