Golang goroutine不能使用函数返回值

时间:2016-05-12 18:03:38

标签: go concurrency goroutine

我希望我可以使用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

至少从语法的角度来看,我没有看到任何与变量范围有关的问题。 ABC的范围由它们居住在其中的块明确定义(在我的示例中,范围是main()函数)。但是,如果这些变量(此处AB)“准备好”可以读取,那么可能更合理的问题是?当然,在getA()getB()完成之前,它们不应该准备好并且可以访问。事实上,这就是我要求的:编译器可以实现场景背后的所有铃声和口哨,以确保执行将被阻止,直到AB准备好消耗(而不是迫使程序员使用通道明确地实现那些等待和口哨。)

这可以使并发编程更加简单,特别是对于计算任务彼此独立的情况。如果真的需要,这些频道仍然可以用于显式通信和同步。

1 个答案:

答案 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 operatorSend statement)。

您的提案:

go A := getA()
go B := getB()

C := A + B // waits till getA() and getB() return

AB的范围是什么? 2合理的答案是a)来自go陈述或go陈述之后。无论哪种方式,我们都应该能够在go语句之后访问它。在go语句之后,它们将在范围内,读取/写入它们的值不应该阻止。

但是如果C := A + B不会阻止(因为它只是读取变量),那么

a)此行AB应该已经填充,这意味着go语句需要等待getA()完成(但是它会失败go陈述的目的)

b)否则我们需要一些外部代码进行同步,但是我们再也没有获得任何东西(只是将其与通道解决方案相比更糟)。

  

Do not communicate by sharing memory; instead, share memory by communicating.

通过使用渠道,很清楚什么(可能)阻止什么不阻止。很明显,当从通道接收完成时,goroutine就完成了。并且它为我们提供了在我们想要的时候执行接收的意思(在需要它的值并且我们愿意等待它的时候),以及检查值是否为准备好没有阻塞(逗号成语)。