Получение возвращаемых значений из горутин

83

Приведенный ниже код дает ошибку компиляции, говорящую о неожиданном запуске:

x := go doSomething(arg)

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

Я знаю, что могу получить возвращаемое значение, если вызову функцию как обычно, без использования горутины. Или я могу использовать каналы и т. Д.

У меня вопрос, почему невозможно получить такое возвращаемое значение из горутины.

Нерв
источник
7
вы можете использовать канал, чтобы вернуть его
rogerdpack
почему это позволяет иметь возвращаемое значение для
горутины
1
@rogerdpack, который требует изменения api любой используемой функции. так что вам может понадобиться функция-оболочка, если она не ваша собственная
Дэвид Калланан

Ответы:

67

Строгий ответ - вы можете это сделать. Вероятно, это не лучшая идея. Вот код, который это сделает:

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

Это создаст новую горутину, которая вычислит, doSomething()а затем назначит результат x. Проблема в том, как вы собираетесь использовать xгорутину из оригинальной? Вы, вероятно, захотите убедиться, что порожденная горутина завершена с ним, чтобы у вас не было состояния гонки. Но если вы хотите это сделать, вам понадобится способ связи с горутиной, и если у вас есть способ сделать это, почему бы просто не использовать его для отправки значения обратно?

Джошлф
источник
6
Вы можете добавить WaitGroup, чтобы убедиться, что вы закончили, и дождитесь его. Но, как вы сказали, это просто не способ, а канал.
Not_a_Golfer 06
1
Это не return, это assignмент
Нидхин Дэвид
95

Почему невозможно получить возвращаемое значение из горутины, присвоив его переменной?

Запуск goroutine (асинхронно) и получение возвращаемого значения из функции - это по сути противоречивые действия. Когда вы говорите, goвы имеете в виду «выполнять асинхронно» или даже проще: «Продолжайте! Не дожидайтесь завершения выполнения функции». Но когда вы присваиваете значение, возвращаемое функцией, переменной, вы ожидаете, что это значение будет внутри переменной. Поэтому, когда вы это делаете, x := go doSomething(arg)вы говорите: «Продолжайте, не ждите функции! Подождите-подождите-подождите! Мне нужно, чтобы возвращаемое значение было доступно в xvar прямо в следующей строке!»

каналы

Самый естественный способ получить значение из горутины - это каналы. Каналы - это каналы, которые соединяют параллельные горутины. Вы можете отправлять значения в каналы из одной горутины и получать эти значения в другую горутину или в синхронной функции. Вы можете легко получить значение из горутины, не нарушающей параллелизма, используя 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

CSP и передача сообщений

Go в большей степени основан на теории CSP . Наивное описание, приведенное выше, можно было бы точно изложить в терминах CSP (хотя я считаю, что это выходит за рамки вопроса). Настоятельно рекомендую ознакомиться с теорией CSP хотя бы потому, что это RAD. Эти короткие цитаты дают направление мышления:

Как следует из названия, CSP позволяет описывать системы в терминах компонентных процессов, которые работают независимо и взаимодействуют друг с другом исключительно посредством обмена сообщениями .

В информатике передача сообщений отправляет сообщение процессу и полагается на процесс и вспомогательную инфраструктуру для выбора и вызова фактического кода для выполнения. Передача сообщений отличается от обычного программирования, в котором процесс, подпрограмма или функция вызываются напрямую по имени.

I159
источник
9

Идея goключевого слова заключается в том, что вы запускаете функцию doSomething асинхронно и продолжаете текущую горутину, не дожидаясь результата, что-то вроде выполнения команды в оболочке Bash с символом «&» после нее. Если ты хочешь сделать

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

тогда вам нужно заблокировать текущую горутину до завершения doSomething. Так почему бы просто не вызвать doSomething в текущей горутине? Есть и другие варианты (например, doSomething может отправлять результат в канал, из которого текущая горутина получает значения), но простой вызов doSomething и присвоение результата переменной, очевидно, проще.

Матрица
источник
0

Это дизайн, выбранный создателями Go. Там целые много абстракций / API для представления значения асинхронных операции ввода / вывод - promise, future, async/await, callback, observableи т.д. Эти абстракции / API , которые по своей природе связаны с единицей планирования - сопрограммы - и эти абстракции / API , диктуют , как сопрограммы ( или, точнее, возвращаемое значение асинхронного ввода-вывода, представленное ими), может быть составлено .

Go выбрал передачу сообщений (также называемых каналами ) в качестве абстракции / API для представления возвращаемого значения операций асинхронного ввода-вывода. И, конечно же, горутины и каналы дают вам составной инструмент для реализации операций асинхронного ввода-вывода.

Бхарат Хатри
источник