以下のコードは、「予期しないgo」というコンパイルエラーを示します。
x := go doSomething(arg)
func doSomething(arg int) int{
...
return my_int_value
}
ゴルーチンを使用せずに関数を通常どおりに呼び出すと、戻り値を取得できます。または、チャネルなどを使用できます。
私の質問は、なぜゴルーチンからこのような戻り値を取得できないのかということです。
厳密な答えは、あなたがcanすることです。たぶん良い考えではありません。これを行うコードは次のとおりです。
_var x int
go func() {
x = doSomething()
}()
_
これは、doSomething()
を計算し、その結果をx
に割り当てる新しいゴルーチンを生成します。問題は、元のゴルーチンからx
をどのように使用するかです。おそらく、スポーンされたゴルーチンがそれで処理されていることを確認して、競合状態がないようにする必要があります。しかし、それをしたい場合は、ゴルーチンと通信する方法が必要になります。それを行う方法がある場合は、単にそれを使用して値を返送しないのですか?
Goroutineを(非同期で)実行し、関数から戻り値を取得することは、本質的に矛盾したアクションです。 go
と言うときは、「非同期で実行する」またはさらに単純なことを意味します。「続けてください!関数の実行が終了するまで待たないでください」。ただし、関数の戻り値を変数に割り当てると、変数内にこの値が含まれることが期待されます。 x := go doSomething(arg)
を実行すると、「続けて、関数を待たずに!Wait-wait-wait!下の次の行でx
varで戻り値にアクセスできるようにする必要があります。 !」
ゴルーチンから値を取得する最も自然な方法はチャネルです。チャネルは、並行ゴルーチンを接続するパイプです。 1つのゴルーチンからチャネルに値を送信し、それらの値を別のゴルーチンまたは同期関数で受信できます。 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)
}
}
}
この例は、Go By Example
Goは、 CSP理論 に大きく基づいています。上記の単純な説明は、CSPの観点から正確に概説できます(ただし、それは質問の範囲外であると思います)。少なくともRADであるため、CSP理論に精通することを強くお勧めします。これらの短い引用は、思考の方向性を示します。
その名前が示すように、CSPは独立して動作するコンポーネントプロセスの観点からシステムの記述を許可し、メッセージパッシング通信。
コンピューターサイエンスでは、メッセージの受け渡しはメッセージをプロセスに送信し、プロセスとサポートするインフラストラクチャに依存して、実行する実際のコードを選択して呼び出します。メッセージの受け渡しは、プロセス、サブルーチン、または関数が名前で直接呼び出される従来のプログラミングとは異なります。
go
キーワードの考え方は、doSomething関数を非同期で実行し、結果を待たずに現在のゴルーチンを続行することです。これは、Bashシェルでコマンドの後に「&」を付けて実行するようなものです。やりたいなら
x := doSomething(arg)
// Now do something with x
その後、doSomethingが終了するまでブロックする現在のゴルーチンが必要です。では、なぜdoSomething inを現在のgoroutineだけで呼び出さないのでしょうか?他のオプションもあります(doSomethingは、現在のゴルーチンが値を受け取るチャネルに結果をポストできます)が、単にdoSomethingを呼び出して変数に結果を割り当てることは明らかに簡単です。
Goクリエイターによるデザインの選択です。非同期I/O操作の値を表す抽象化/ APIがたくさんあります-promise
、future
、async/await
、callback
、observable
など。これらの抽象化/ APIは、スケジューリングの単位-coroutines-およびこれらの抽象化/ APIは、コルーチン(またはより正確には、それらによって表される非同期I/Oの戻り値)をcomposedにする方法を指示します。
Goは、非同期I/O操作の戻り値を表す抽象化/ APIとしてメッセージパッシング(別名channels)を選択しました。そしてもちろん、ゴルーチンとチャネルは、非同期I/O操作を実装するための構成可能なツールを提供します。