Улавливание возвращаемых значений из гортанов
В приведенном ниже коде выдается сообщение об ошибке компиляции: "неожиданный ход"
x := go doSomething(arg)
func doSomething(arg int) int{
...
return my_int_value
}
Я знаю, что могу получить возвращаемое значение, если вызову функцию нормально, без использования goroutine. Или я могу использовать каналы и т.д.
Мой вопрос: почему невозможно получить возвращаемое значение из этой программы?
Ответы
Ответ 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)