英文:
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 <- "one"
}()
go func() {
time.Sleep(time.Second * 2)
c2 <- "two"
}()
for i := 0; i < 2; i++ {
// Await both of these values
// simultaneously, printing each one as it arrives.
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", 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操作的值 - promise
,future
,async/await
,callback
,observable
等等。这些抽象/ 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 := <- chanRes
func doSomething(arg int, out chan<- int){
...
out <- my_int_value
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论