Le code ci-dessous donne une erreur de compilation en disant 'inattendue':
x := go doSomething(arg)
func doSomething(arg int) int{
...
return my_int_value
}
Je sais que je peux récupérer la valeur de retour si la fonction est appelée normalement, sans utiliser goroutine. Ou je peux utiliser des canaux, etc.
Ma question est la suivante: pourquoi est-il impossible de récupérer une valeur de retour comme celle-ci à partir d’un goroutine?.
La réponse stricte est que vous pouvez faites cela. Ce n'est probablement pas une bonne idée. Voici le code qui ferait cela:
var x int
go func() {
x = doSomething()
}()
Cela créera un nouveau goroutine qui calculera doSomething()
, puis assignera le résultat à x
. Le problème est le suivant: comment allez-vous utiliser x
depuis le goroutine original? Vous voulez probablement vous assurer que le goroutine engendré en a fini afin que vous n'ayez pas une condition de concurrence critique. Mais si vous voulez faire cela, vous aurez besoin d'un moyen de communiquer avec le goroutine, et si vous avez un moyen de le faire, pourquoi ne pas simplement l'utiliser pour renvoyer la valeur?
Exécuter goroutine (de manière asynchrone) et extraire la valeur de retour de function sont essentiellement des actions contradictoires. Lorsque vous dites go
, vous voulez dire "faites-le de manière asynchrone" ou même plus simplement: "Allez-y! N'attendez pas que l'exécution de la fonction soit terminée". Mais lorsque vous affectez une valeur de retour de fonction à une variable, vous vous attendez à ce que cette valeur soit comprise dans la variable. Donc, quand vous faites cela x := go doSomething(arg)
vous dites: "Allez, n'attendez pas la fonction! Wait-wait-wait! J'ai besoin qu'une valeur renvoyée soit accessible dans x
var right dans la ligne suivante ci-dessous! "
Les canaux constituent le moyen le plus naturel de récupérer une valeur dans un goroutine. Les canaux sont les canaux qui connectent les goroutines concurrentes. Vous pouvez envoyer des valeurs dans les canaux à partir d'une goroutine et les recevoir dans une autre goroutine ou dans une fonction synchrone. Vous pouvez facilement obtenir une valeur d'un goroutine sans interrompre la concurrence en utilisant 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)
}
}
}
L'exemple est tiré de Go By Example
Go est plus largement basé sur théorie CSP . La description naïve ci-dessus pourrait être décrite avec précision en termes de CSP (bien que je pense que cela sort du cadre de la question). Je recommande fortement de vous familiariser avec la théorie CSP au moins parce que c'est RAD. Ces courtes citations donnent un sens à la pensée:
Comme son nom l'indique, CSP permet de décrire les systèmes en termes de processus de composants qui fonctionnent indépendamment et n'interagissent les uns avec les autres que par le biais de communication par message .
En informatique, la transmission de messages envoie un message à un processus et s’appuie sur le processus et sur l’infrastructure d’appui pour sélectionner et appeler le code à exécuter. La transmission de message diffère de la programmation classique dans laquelle un processus, un sous-programme ou une fonction est directement appelé par son nom.
L'idée du mot clé go
est que vous exécutez la fonction doSomething de manière asynchrone et que vous continuiez le goroutine actuel sans attendre le résultat, un peu comme si vous exécutiez une commande dans un shell Bash avec le signe '&'. Si tu veux faire
x := doSomething(arg)
// Now do something with x
alors vous avez besoin du goroutine actuel pour bloquer jusqu'à ce que quelque chose se termine. Alors pourquoi ne pas simplement appeler quelque chose en le goroutine actuel? Il existe d'autres options (par exemple, quelque chose peut-il afficher un résultat sur un canal, d'où le goroutine actuel reçoit des valeurs), mais simplement appeler doSomething et affecter le résultat à une variable est évidemment plus simple.
C'est un choix de conception par les créateurs de Go. Il y a beaucoup d'abstractions/API pour représenter la valeur des opérations d'E/S asynchrones - promise
, future
, async/await
, callback
, observable
, etc. Ces abstractions/API sont intrinsèquement liées à l’unité de planification - coroutines - et ces abstractions/API dictent comment les coroutines ( ou plus précisément, la valeur de retour des E/S asynchrones représentées par celles-ci) peut être composée .
Go a choisi un message en passant (aka canaux ) en tant qu'abstraction/API pour représenter la valeur de retour des opérations d'E/S asynchrones. Et bien sûr, les goroutines et les canaux vous fournissent un outil composable pour la mise en oeuvre d'opérations d'E/S asynchrones.