Go常用的并发机制:协程

16
在Go语言中,如果我们有一个带有某个方法的类型,该方法启动一些循环机制(不断轮询A并执行B),最好是这样表达:
// Run does stuff, you probably want to run this as a goroutine
func (t Type) Run() {
    // Do long-running stuff
}

并记录下这可能需要作为goroutine启动(并让调用者处理)

或者将其隐藏在调用者之外:

// Run does stuff concurrently
func (t Type) Run() {
   go DoRunStuff()
}

我是一名新手,对于Go语言中异步运行的代码,不确定惯例是否要求让调用者添加'go'前缀。
目前我的看法是应该记录并给调用者选择。我的想法是在Go语言中,并发实际上并不是公开接口的一部分,而是使用它的属性。这样说对吗?

到目前为止,有两个相互对立的答案得票相等。除非Rob Pike或其他人回答,否则这可能是一个具体情况。:P - Aiden Bell
它们不一定是相反的答案。我认为两个答案都表示在某些情况下可以做到这一点。我只是尝试给出更一般的建议,超出了你的示例范围。 - zmb
您可能会对今年的GopherConIndia相关视频感兴趣:https://www.youtube.com/watch?v=Y6KnnRh6wVo&list=PLxFC1MYuNgJTY3uQ5Ja4F5Sz305nnrBOq&index=14 - zmb
3个回答

10

在我编写一个适配器以实现Web服务的并发性之前,我对此持有与您相同的观点。我必须启动一个Go协程来解析从Web调用返回到通道的结果。没有任何情况下,这个API可以不使用作为Go协程。

然后我开始研究像net/http这样的包。该包中有强制并发性。文档在接口级别上记录应该能够并发使用,但默认实现会自动使用Go协程。

由于Go的标准库通常在其自己的包中触发Go协程,因此我认为如果您的包或API需要,您可以自行处理它们。


感谢您的回复并使用Go标准库中的示例,我将去查找它们。 - Aiden Bell

9
我的当前观点是我们应该记录并给调用者一个选择。
我倾向于同意你的观点。
由于Go使得并发运行代码变得非常容易,因此您应该尽量避免在API中使用并发(这将强制客户端同时使用它)。相反,创建一个同步的API,然后客户端可以选择同步或并发运行它。
几年前的一次演讲中曾经讨论过这个问题:Twelve Go Best Practices
特别是第26张幻灯片展示了更像您第一个示例的代码。
我认为net/http包是一个例外,因为在这种情况下,并发几乎是必须的。如果该包没有在内部使用并发,则客户端代码几乎肯定会使用并发。例如,http.Client不会(据我所知)启动任何goroutine。只有服务器才会这样做。
在大多数情况下,对于调用者来说,这将是一行代码: go Run()StartGoroutine()
同步 API 在并发使用时并不更难,并且给调用者提供了更多的选项。

谢谢您的回复。第25页非常有帮助。我也遇到了一些困难,因为边缘情况设计位于“http”包中,其服务器/客户端/处理程序用途相当标准。嗯,这可能是个棘手的问题。 - Aiden Bell
在我的情况下,第28张幻灯片(http://talks.golang.org/2013/bestpractices.slide#28)与我的代码非常相似,因为我的“构造函数”是用于服务器API的,并且具有类似第29张幻灯片的kill chan。然而,在第28张幻灯片中,构造函数启动了goroutine,这使得用户无法进行选择。 - Aiden Bell
可能是服务器API是第25页的例外情况。 - Aiden Bell

1

没有“正确”的答案,因为情况各不相同。

显然,有些情况下,API可能包含实用程序、简单算法、数据集合等,如果将它们打包成goroutines,看起来会很奇怪。

相反,有些情况下,自然而然地期望“底层”并发,比如一个丰富的IO库(http服务器是明显的例子)。

对于更极端的情况,考虑一下你要制作一个插拔式并发服务库。这样的API由模块组成,每个模块都通过通道具有良好描述的接口。显然,在这种情况下,它不可避免地涉及到goroutines作为API的一部分。

一个线索可能是函数参数中通道的存在或不存在。但我希望无论哪种情况,都能清楚地说明可以期望什么。


我同意,当然我们可以做我们想做的事情。我不是在寻找正确答案,而是期望别人调用API时可能会有的任何期望。 - Aiden Bell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接