Подтвердить что ты не робот

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

В приведенном ниже коде выдается сообщение об ошибке компиляции: "неожиданный ход"

x := go doSomething(arg)

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

каналы

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

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

Ответ 3

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

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

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

Ответ 4

Лучше всего использовать какой-то замок/мьютекс, когда goroutines взаимодействуют с внешними состояниями.

m := sync.Mutex{}
m.Lock()
var x int

func doSomething(arg int){
    go func() {
        err = work()
        if err != nil {
            x = 500
        } else {
            x = 200
        }
        m.Unlock()
    }()
}   

// You can do something like this
// And the goroutine will update x correctly.
doSomething(3)