从goroutine中捕获返回值

huangapple go评论88阅读模式
英文:

Catching return values from goroutines

问题

下面的代码会出现编译错误,错误信息是“意外的go关键字”:

x := go doSomething(arg)

func doSomething(arg int) int{
    ...
    return my_int_value
}

我知道,如果我正常调用函数,即不使用goroutine或使用通道等方式,我可以获取返回值。

我的问题是,为什么不能像这样从goroutine中获取返回值呢?

英文:

The below code gives compilation error saying 'unexpected go':

x := go doSomething(arg)

func doSomething(arg int) int{
    ...
    return my_int_value
}

I know, I can fetch the return value if I call the function normally i.e. without using goroutine or I can use channels etc.

My question is why is it not possible to fetch a return value like this from a goroutine.

答案1

得分: 167

为什么无法从goroutine中获取返回值并将其赋给变量?

运行goroutine(异步执行)并从函数中获取返回值实际上是相互矛盾的操作。当你使用go关键字时,意思是“以异步方式执行”,甚至更简单地说:“继续!不要等待函数执行完成”。但是当你将函数的返回值赋给一个变量时,你期望在变量中得到这个值。所以当你写下x := go doSomething(arg)时,你的意思是:“继续,不要等待函数!等等等等!我需要在下一行中访问到返回值,并存储在变量x中!”

通道

从goroutine中获取值的最自然的方式是使用通道(channels)。通道是连接并发goroutine的管道。你可以从一个goroutine中将值发送到通道中,然后在另一个goroutine或同步函数中接收这些值。你可以使用select语句轻松地从goroutine中获取值而不破坏并发性:

func main() {
    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(time.Second * 1)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c2 <- "two"
    }()

    for i := 0; i < 2; i++ {
        // 同时等待这两个值,每个值到达时打印它们。
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        } 
    }
}

这个例子来自于Go By Example

CSP和消息传递

Go在很大程度上基于CSP理论。上面的简单描述可以用CSP的术语精确地概述(尽管我认为这超出了问题的范围)。我强烈建议你至少熟悉一下CSP理论,因为它非常强大。以下是一些简短的引用,可以指导思考:

正如其名称所示,CSP允许使用独立运行的组件进程来描述系统,并且这些进程仅通过消息传递通信与彼此交互。

在计算机科学中,消息传递是向进程发送消息,并依赖于进程和支持基础设施来选择和调用实际要运行的代码。消息传递与传统的编程方式不同,传统的方式是通过名称直接调用进程、子程序或函数。

英文:

Why is it not possible to fetch a return value from a goroutine assigning it to a variable?

Run goroutine (asynchronously) and fetch return value from function are essentially contradictory actions. When you say go you mean "do it asynchronously" or even simpler: "Go on! Don't wait for the function execution be finished". But when you assign function return value to a variable you are expecting to have this value within the variable. So when you do that x := go doSomething(arg) you are saying: "Go on, don't wait for the function! Wait-wait-wait! I need a returned value be accessible in x var right in the next line below!"

Channels

The most natural way to fetch a value from a goroutine is channels. Channels are the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine or in a synchronous function. You could easily obtain a value from a goroutine not breaking concurrency using select:

func main() {

    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(time.Second * 1)
        c1 &lt;- &quot;one&quot;
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c2 &lt;- &quot;two&quot;
    }()

    for i := 0; i &lt; 2; i++ {
        // Await both of these values
        // simultaneously, printing each one as it arrives.
        select {
        case msg1 := &lt;-c1:
            fmt.Println(&quot;received&quot;, msg1)
        case msg2 := &lt;-c2:
            fmt.Println(&quot;received&quot;, msg2)
        } 
    }
}

The example is taken from Go By Example

CSP & message-passing

Go is largerly based on CSP theory. The naive description from above could be precisely outlined in terms of CSP (although I believe it is out of scope of the question). I strongly recommend to familiarize yourself with CSP theory at least because it is RAD. These short quotations give a direction of thinking:

> As its name suggests, CSP allows the description of systems in terms of component processes that operate independently, and interact with each other solely through message-passing communication.
>
> In computer science, message passing sends a message to a process and relies on the process and the supporting infrastructure to select and invoke the actual code to run. Message passing differs from conventional programming where a process, subroutine, or function is directly invoked by name.

答案2

得分: 89

严格来说,你可以这样做。只是这可能不是一个好主意。以下是可以实现这个功能的代码:

var x int
go func() {
    x = doSomething()
}()

这将创建一个新的 goroutine,计算 doSomething() 的结果,并将结果赋值给 x。问题是:你如何从原始的 goroutine 中使用 x?你可能希望确保新创建的 goroutine 使用完 x 后再进行操作,以避免竞态条件。但如果你想要这样做,你需要一种与 goroutine 进行通信的方式,如果你已经有了这种方式,为什么不直接使用它将值发送回来呢?

英文:

The strict answer is that you can do that. It's just probably not a good idea. Here's code that would do that:

var x int
go func() {
    x = doSomething()
}()

This will spawn off a new goroutine which will calculate doSomething() and then assign the result to x. The problem is: how are you going to use x from the original goroutine? You probably want to make sure the spawned goroutine is done with it so that you don't have a race condition. But if you want to do that, you'll need a way to communicate with the goroutine, and if you've got a way to do that, why not just use it to send the value back?

答案3

得分: 9

go 关键字的思想是以异步方式运行 doSomething 函数,并在不等待结果的情况下继续当前的 goroutine,有点像在 Bash shell 中执行一个命令后面加上 &。如果你想要执行以下操作:

x := doSomething(arg)
// 现在对 x 做一些操作

那么你需要当前的 goroutine 阻塞,直到 doSomething 完成。那为什么不直接在当前的 goroutine 中调用 doSomething 呢?还有其他选项(比如,doSomething 可以将结果发送到一个通道,当前的 goroutine 从中接收值),但是简单地调用 doSomething 并将结果赋值给一个变量显然更简单。

英文:

The idea of the go keyword is that you run the doSomething function asynchronously, and continue the current goroutine without waiting for the result, kind of like executing a command in a Bash shell with an '&' after it. If you want to do

x := doSomething(arg)
// Now do something with x

then you need the current goroutine to block until doSomething finishes. So why not just call doSomething in the current goroutine? There are other options (like, doSomething could post a result to a channel, which the current goroutine receives values from) but simply calling doSomething and assigning the result to a variable is obviously simpler.

答案4

得分: 6

这是Go语言创作者的设计选择。有很多抽象/ API可以表示异步I/O操作的值 - promisefutureasync/awaitcallbackobservable等等。这些抽象/ API与调度单元 - 协程 - 密切相关,并且这些抽象/ API决定了协程(或更准确地说,由它们表示的异步I/O的返回值)如何进行组合

Go语言选择了消息传递(也称为通道)作为表示异步I/O操作的返回值的抽象/ API。当然,goroutine和channel为您提供了一种可组合的工具来实现异步I/O操作。

英文:

It's a design choice by Go creators. There's a whole lot of abstractions/APIs to represent the value of async I/O operations - promise, future, async/await, callback, observable, etc. These abstractions/APIs are inherently tied to the unit of scheduling - coroutines - and these abstractions/APIs dictate how coroutines (or more precisely the return value of async I/O represented by them) can be composed.

Go chose message passing (aka channels) as the abstraction/API to represent the return value of async I/O operations. And of course, goroutines and channels give you a composable tool to implement async I/O operations.

答案5

得分: 4

为什么不使用通道进行写入?

chanRes := make(chan int, 1)
go doSomething(arg, chanRes)
// 在这里阻塞,或者你可以使用其他同步机制(做其他事情)并等待
x := <- chanRes
func doSomething(arg int, out chan<- int){
    ...
    out <- my_int_value
}
英文:

Why not use a channel to write into?

chanRes := make(chan int, 1)
go doSomething(arg, chanRes)
//blocks here or you can use some other sync mechanism (do something else) and wait
x := &lt;- chanRes
func doSomething(arg int, out chan&lt;- int){
    ...
    out &lt;- my_int_value
}

huangapple
  • 本文由 发表于 2014年1月6日 15:28:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/20945069.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定