Captura de valores de retorno de goroutines

82

El siguiente código da un error de compilación que dice 'marcha inesperada':

x := go doSomething(arg)

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

Lo sé, puedo recuperar el valor de retorno si llamo a la función normalmente, sin usar goroutine. O puedo usar canales, etc.

Mi pregunta es por qué no es posible obtener un valor de retorno como este de una goroutine.

Nervio
fuente
7
podría usar un canal para devolverlo
rogerdpack
¿Por qué permite tener un valor de retorno para una goroutine?
srinath samala
1
@rogerdpack que requiere cambiar la api de cualquier función que esté utilizando. por lo que es posible que necesite una función contenedora si no es la suya propia
David Callanan

Respuestas:

66

La respuesta estricta es que se puede hacer eso. Probablemente no sea una buena idea. Aquí hay un código que haría eso:

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

Esto generará una nueva rutina de gor que calculará doSomething()y luego asignará el resultado a x. El problema es: ¿cómo se va a utilizar xde la goroutine original? Probablemente quieras asegurarte de que la goroutine generada se haya completado para que no tengas una condición de carrera. Pero si quiere hacer eso, necesitará una forma de comunicarse con la goroutine, y si tiene una forma de hacerlo, ¿por qué no usarla para devolver el valor?

Joshlf
fuente
6
Puede agregar un WaitGroup para asegurarse de haber terminado y esperarlo. Pero como dijiste, simplemente no es la forma de hacerlo, un canal lo es.
Not_a_Golfer
1
Esto no es return, esto es assignment
Nidhin David
94

¿Por qué no es posible obtener un valor de retorno de una goroutine asignándolo a una variable?

Ejecutar goroutine (asincrónicamente) y obtener el valor de retorno de la función son acciones esencialmente contradictorias. Cuando dice goque quiere decir "hacerlo asincrónicamente" o incluso más simple: "¡Adelante! No espere a que finalice la ejecución de la función". Pero cuando asigna un valor de retorno de función a una variable, espera tener este valor dentro de la variable. Entonces, cuando haces eso, x := go doSomething(arg)estás diciendo: "¡Continúa, no esperes la función! ¡Espera-espera-espera! ¡Necesito que se pueda acceder a un valor devuelto en xvar en la siguiente línea!"

Canales

La forma más natural de obtener un valor de una gorutina son los canales. Los canales son las tuberías que conectan gorutinas concurrentes. Puede enviar valores a canales desde una goroutine y recibir esos valores en otra goroutine o en una función sincrónica. Puede obtener fácilmente un valor de una goroutine que no rompa la concurrencia usando 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)
        } 
    }
}

El ejemplo está tomado de Go By Example

CSP y transmisión de mensajes

Go se basa en gran medida en la teoría CSP . La descripción ingenua de arriba podría describirse con precisión en términos de CSP (aunque creo que está fuera del alcance de la pregunta). Recomiendo encarecidamente familiarizarse con la teoría de la CSP al menos porque es RAD. Estas breves citas dan una dirección de pensamiento:

Como su nombre indica, CSP permite la descripción de sistemas en términos de procesos componentes que operan de forma independiente e interactúan entre sí únicamente a través de la comunicación de paso de mensajes .

En informática, el paso de mensajes envía un mensaje a un proceso y se basa en el proceso y la infraestructura de apoyo para seleccionar e invocar el código real para su ejecución. El paso de mensajes difiere de la programación convencional donde un proceso, subrutina o función se invoca directamente por su nombre.

I159
fuente
9

La idea de la gopalabra clave es que ejecute la función doSomething de forma asincrónica y continúe con la goroutine actual sin esperar el resultado, algo así como ejecutar un comando en un shell Bash con un '&' después. Si quieres hacer

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

entonces necesita la goroutine actual para bloquear hasta que doSomething termine. Entonces, ¿por qué no simplemente llamar a doSomething en la goroutine actual? Hay otras opciones (como, doSomething podría publicar un resultado en un canal, del cual la goroutine actual recibe valores) pero simplemente llamar a doSomething y asignar el resultado a una variable es obviamente más simple.

MatrixFrog
fuente
0

Es una elección de diseño de los creadores de Go. Hay un montón de abstracciones / API para representar el valor de asíncrono operaciones I / O - promise, future, async/await, callback, observable, etc. Estas abstracciones / API son inherentemente vinculada a la unidad de programación - corrutinas - y estas abstracciones / API dictar cómo corrutinas ( o más precisamente el valor de retorno de la E / S asíncrona representada por ellos) se puede componer .

Go eligió el paso de mensajes (también conocido como canales ) como abstracción / API para representar el valor de retorno de las operaciones de E / S asíncronas. Y, por supuesto, las rutinas y canales le brindan una herramienta componible para implementar operaciones de E / S asíncronas.

Bharat Khatri
fuente