当步骤是连续的时使用 goroutines
Use of goroutines when steps are sequential
我觉得我的问题的答案是否定的,而是要求确定性,因为我才开始使用 Go 几天。我们是否应该将 IO 绑定任务(如 http 请求)封装到 goroutine 中,即使它是用于顺序用例?
这是我的天真的例子。假设我有一个发出 3 个 http 请求但需要按顺序执行的方法。将 invoke
方法创建为 goroutines 有什么好处吗?我知道下面的示例实际上会影响性能。
func myMethod() {
chan1 := make(chan int)
chan2 := make(chan int)
chan3 := make(chan int)
go invoke1(chan1)
res1 := <-chan1
invoke2(res1, chan2)
res2 := <-chan2
invoke3(res2, chan3)
// Do something with <-chan3
}
我想到的一个可能原因是为了将来证明 invoke
方法在其他开发人员开始重新使用该方法时在并发上下文中调用它们的时间。还有其他原因吗?
对于这个问题,没有任何标准可以说是或否。
虽然您可以通过这种方式正确执行,但坚持简单的顺序执行要简单得多。
想到三个原因:
- 这仍然是顺序的:你在顺序地等待每一个 goroutine,所以这对你没有任何好处。如果只做一个 http 请求,性能可能不会有太大变化,这两种情况都会花费大部分时间等待响应。
- 错误处理 如果你只得到
result, err := invoke; if err != nil ....
而不是必须通过通道传递结果和错误 就简单多了
- 过度概括 比 "future proofing" 更贴切。如果您以后需要异步调用您的
invoke
方法,请在以后更改您的代码。然后在您的函数周围添加异步包装器将同样容易。
您可以使用像 buf 这样的通道,或使用 []string(字符串 url 片段)。如果你只需要顺序执行,你不会从 goroutines 中得到任何好处,因为我们无法在 goroutine 启动时控制它。
但是我们可以问然后不要等待runtime.Gosched()
来自文档:
Gosched 让出处理器,允许其他 goroutines 运行。它不会暂停当前的 goroutine,因此执行会自动恢复。
顺序执行示例:
package main
func main() {
urls := []string{
"https://google.com",
"https://yahoo.com",
"https://youtube.com",
}
buf := make([][]byte, 0, len(urls))
for _, v := range urls {
buf = append(buf, sendRequest(v))
}
}
func sendRequest(url string) []byte {
//send
return []byte("")
}
对于这样的解决方案,我认为按照您在现实世界中的方式解决您的问题没有任何好处。这并不意味着您的解决方案没有收益。如果您正在练习例如同步 go 例程,那么您有大量的可能性进行实验和学习。
所以对我来说,这远不是明确的否定,但也不是明确的肯定。我不会说也许,这取决于你的具体目标。您是在真正解决问题或学习之后吗?
我来晚了一点,但我想我还有一些东西要分享。
在回答这个问题之前,我想深入了解一下 Go 谚语,并发不是并行。人们对goroutine和Go语言特性的一个非常普遍的误解是,当人们想到goroutine时,他们会想到并行的能力。
但正如 Rob Pike 在许多 Go Talks 中指出的那样,Go 和 goroutine 实际上提供的是并发性。 并发是一种更好地理解现实世界的模型,一种代码的方式和结构,代码如何交互。
回到问题。当步骤是顺序的时候应该使用goroutine吗?这取决于设计。如果您的代码由彼此非常自然地对话的各个部分组成,或者如果您的某些代码保留状态并且经常 return
只是没有意义,或者如果您的代码适合任何其他并发设计,它是使用 goroutine 和 channel 以及 select
语句非常好。 Rob Pike 再次就 Go 的 text/template
使用的词法分析器进行了 Go Talk,其中词法分析器使用 goroutine,但解析器显然只按顺序使用词法分析器。他表示,通过使用 goroutine 和 channel,以牺牲一点性能为代价,实现了更好的API。
但另一方面,在您的示例中以及您可能正在考虑的内容(可能需要未来并行处理的代码),我同意@Marc。坚持屏蔽来电,至少现在是这样。
我觉得我的问题的答案是否定的,而是要求确定性,因为我才开始使用 Go 几天。我们是否应该将 IO 绑定任务(如 http 请求)封装到 goroutine 中,即使它是用于顺序用例?
这是我的天真的例子。假设我有一个发出 3 个 http 请求但需要按顺序执行的方法。将 invoke
方法创建为 goroutines 有什么好处吗?我知道下面的示例实际上会影响性能。
func myMethod() {
chan1 := make(chan int)
chan2 := make(chan int)
chan3 := make(chan int)
go invoke1(chan1)
res1 := <-chan1
invoke2(res1, chan2)
res2 := <-chan2
invoke3(res2, chan3)
// Do something with <-chan3
}
我想到的一个可能原因是为了将来证明 invoke
方法在其他开发人员开始重新使用该方法时在并发上下文中调用它们的时间。还有其他原因吗?
对于这个问题,没有任何标准可以说是或否。
虽然您可以通过这种方式正确执行,但坚持简单的顺序执行要简单得多。
想到三个原因:
- 这仍然是顺序的:你在顺序地等待每一个 goroutine,所以这对你没有任何好处。如果只做一个 http 请求,性能可能不会有太大变化,这两种情况都会花费大部分时间等待响应。
- 错误处理 如果你只得到
result, err := invoke; if err != nil ....
而不是必须通过通道传递结果和错误 就简单多了
- 过度概括 比 "future proofing" 更贴切。如果您以后需要异步调用您的
invoke
方法,请在以后更改您的代码。然后在您的函数周围添加异步包装器将同样容易。
您可以使用像 buf 这样的通道,或使用 []string(字符串 url 片段)。如果你只需要顺序执行,你不会从 goroutines 中得到任何好处,因为我们无法在 goroutine 启动时控制它。
但是我们可以问然后不要等待runtime.Gosched()
来自文档: Gosched 让出处理器,允许其他 goroutines 运行。它不会暂停当前的 goroutine,因此执行会自动恢复。
顺序执行示例:
package main
func main() {
urls := []string{
"https://google.com",
"https://yahoo.com",
"https://youtube.com",
}
buf := make([][]byte, 0, len(urls))
for _, v := range urls {
buf = append(buf, sendRequest(v))
}
}
func sendRequest(url string) []byte {
//send
return []byte("")
}
对于这样的解决方案,我认为按照您在现实世界中的方式解决您的问题没有任何好处。这并不意味着您的解决方案没有收益。如果您正在练习例如同步 go 例程,那么您有大量的可能性进行实验和学习。
所以对我来说,这远不是明确的否定,但也不是明确的肯定。我不会说也许,这取决于你的具体目标。您是在真正解决问题或学习之后吗?
我来晚了一点,但我想我还有一些东西要分享。
在回答这个问题之前,我想深入了解一下 Go 谚语,并发不是并行。人们对goroutine和Go语言特性的一个非常普遍的误解是,当人们想到goroutine时,他们会想到并行的能力。
但正如 Rob Pike 在许多 Go Talks 中指出的那样,Go 和 goroutine 实际上提供的是并发性。 并发是一种更好地理解现实世界的模型,一种代码的方式和结构,代码如何交互。
回到问题。当步骤是顺序的时候应该使用goroutine吗?这取决于设计。如果您的代码由彼此非常自然地对话的各个部分组成,或者如果您的某些代码保留状态并且经常 return
只是没有意义,或者如果您的代码适合任何其他并发设计,它是使用 goroutine 和 channel 以及 select
语句非常好。 Rob Pike 再次就 Go 的 text/template
使用的词法分析器进行了 Go Talk,其中词法分析器使用 goroutine,但解析器显然只按顺序使用词法分析器。他表示,通过使用 goroutine 和 channel,以牺牲一点性能为代价,实现了更好的API。
但另一方面,在您的示例中以及您可能正在考虑的内容(可能需要未来并行处理的代码),我同意@Marc。坚持屏蔽来电,至少现在是这样。