我希望我可以使用golang执行以下代码:
package main
import (
"fmt"
"time"
)
func getA() (int) {
fmt.Println("getA: Calculating...")
time.Sleep(300 * time.Millisecond)
fmt.Println("getA: Done!")
return 100
}
func getB() (int) {
fmt.Println("getB: Calculating...")
time.Sleep(400 * time.Millisecond)
fmt.Println("getB: Done!")
return 200
}
func main() {
A := go getA()
B := go getB()
C := A + B // waits till getA() and getB() return
fmt.Println("Result:", C)
fmt.Println("All done!")
}
更具体地说,我希望Go可以处理幕后的并发。
这可能有点偏离主题,但我很好奇人们对这种隐式并发支持的看法。值得为它付出一些努力吗?什么是潜在的困难和缺点?
修改
要明确的是,问题不在于“Go现在正在做什么?”,而不是“它是如何实施的?”虽然我很欣赏@icza的帖子,关于我们应该期待什么,就像现在一样。问题是为什么它不能或不能返回值,以及这样做的潜在复杂性是什么?
回到我的简单例子:
A := go getA()
B := go getB()
C := A + B // waits till getA() and getB() return
至少从语法的角度来看,我没有看到任何与变量范围有关的问题。 A
,B
和C
的范围由它们居住在其中的块明确定义(在我的示例中,范围是main()
函数)。但是,如果这些变量(此处A
和B
)“准备好”可以读取,那么可能更合理的问题是?当然,在getA()
和getB()
完成之前,它们不应该准备好并且可以访问。事实上,这就是我要求的:编译器可以实现场景背后的所有铃声和口哨,以确保执行将被阻止,直到A
和B
准备好消耗(而不是迫使程序员使用通道明确地实现那些等待和口哨。)
这可以使并发编程更加简单,特别是对于计算任务彼此独立的情况。如果真的需要,这些频道仍然可以用于显式通信和同步。
答案 0 :(得分:10)
但这很容易且惯用。该语言提供了手段:Channel types。
只需将通道传递给函数,并让函数在通道上发送结果而不是返回它们。频道可以安全地同时使用。
在任何给定时间只有一个goroutine可以访问该值。根据设计,数据竞争不会发生。
有关详情,请查看问题:If I am using channels properly should I need to use mutexes?
示例解决方案:
func getA(ch chan int) {
fmt.Println("getA: Calculating...")
time.Sleep(300 * time.Millisecond)
fmt.Println("getA: Done!")
ch <- 100
}
func getB(ch chan int) {
fmt.Println("getB: Calculating...")
time.Sleep(400 * time.Millisecond)
fmt.Println("getB: Done!")
ch <- 200
}
func main() {
cha := make(chan int)
chb := make(chan int)
go getA(cha)
go getB(chb)
C := <-cha + <-chb // waits till getA() and getB() return
fmt.Println("Result:", C)
fmt.Println("All done!")
}
输出(在Go Playground上尝试):
getB: Calculating...
getA: Calculating...
getA: Done!
getB: Done!
Result: 300
All done!
注意:强>
以上示例也可以使用单个通道实现:
func main() {
ch := make(chan int)
go getA(ch)
go getB(ch)
C := <-ch + <-ch // waits till getA() and getB() return
fmt.Println("Result:", C)
fmt.Println("All done!")
}
输出是一样的。请在Go Playground上尝试此变体。
修改强>
丢弃此类函数的返回值的Go spec states。更多相关信息:What happens to return value from goroutine。
你建议从多处伤口流血。 Go中的每个变量都有一个scope(可以在其中引用它们)。访问变量不会阻止。语句或运算符的执行可能会阻止(例如Receive operator或Send statement)。
您的提案:
go A := getA()
go B := getB()
C := A + B // waits till getA() and getB() return
A
和B
的范围是什么? 2合理的答案是a)来自go
陈述或go
陈述之后。无论哪种方式,我们都应该能够在go
语句之后访问它。在go
语句之后,它们将在范围内,读取/写入它们的值不应该阻止。
但是如果C := A + B
不会阻止(因为它只是读取变量),那么
a)此行A
和B
应该已经填充,这意味着go
语句需要等待getA()
完成(但是它会失败go
陈述的目的)
b)否则我们需要一些外部代码进行同步,但是我们再也没有获得任何东西(只是将其与通道解决方案相比更糟)。
Do not communicate by sharing memory; instead, share memory by communicating.
通过使用渠道,很清楚什么(可能)阻止什么不阻止。很明显,当从通道接收完成时,goroutine就完成了。并且它为我们提供了在我们想要的时候执行接收的意思(在需要它的值并且我们愿意等待它的时候),以及检查值是否为准备好没有阻塞(逗号成语)。