почему результат вложенной горутины не выводится и как это исправить?
Бьюсь за понимание горутин и каналов. Тема для меня оказалась сложной и один момент не дает мне покоя(так как я его не понимаю). Я приведу полный код, так как не уверен, что проблема в функции somework(хотя почти наверняка в ней).
Данная задача является полностью выдуманной чтобы разобраться:
Как мне продолжать выполнение всех горутин, в т.ч main, когда одна из вложенных горутин выполняет длительную операцию...
Все свои мысли я написал в комментах к коду, в т.ч пометил место, которое у меня вызвает вопрос. Поэтому welcome сразу в код. Спасибо за ваше внимание и время.
package main
import (
"fmt"
"io/ioutil"
"os"
"time"
)
func main() {
start := time.Now().UnixNano()
in, out, wait := make(chan int), make(chan int), make(chan bool)
go somework(in, out, wait, 12)
// записываем рандомные данные в канал который потом
// передадим в качестве аргумена в somework
in <- int(time.Now().Unix())
// выводим результат работы somework
fmt.Printf("somework вернула в канале значение: %v\n", <-out)
end := time.Now().UnixNano()
fmt.Printf("функция main завершилась через >>%v<< наносекунд после своего начала, выполнив все второстепенные горутины\n", end-start)
// ждем, пока somework просигнализирует об окончании своей работы.
<-wait
}
// некая функция, которая внутри себя должна отправить на выполнение длительную операцию.
// все горутины, включая main должны продолжать свое выполнение и когда завершится длительная
// операция вывести результат этой длительной операции независмо от того что в этот момент
// будет делать функция main
func somework(ch, out chan int, wait chan bool, i int) chan int {
start := time.Now().UnixNano()
go func() {
rsultOfrollingAction := make(chan int)
// далее запускаю длительную операцию чтения файла в отдельной горутине
go func(rsultOfrollingAction chan int) {
d := make(chan bool) //для сигнализации об окончании длительного действия
// начало длительной операции в отдельной горутине
// результат данной работы, при имеющемся коде, не выводится.
// ДАННЫЙ ВОПРОС ОТНОСТИИТСЯ КАК РАЗ К ЭТОЙ ПРОБЛЕМЕ
go func(d chan bool) {
time.Sleep(time.Second * 3)
text := readf("text.txt")
rsultOfrollingAction <- len(text)
// сигнализируем горутине которая должна вывести количество
// прочитанных строк, что длительная операция завершене
d <- true
}(d)
// когда d получает сигнал об окончании длительной операции
<-d
// выводим результат длительной операции.
fmt.Printf("вложенная в somework горутина завершила выполенение прочитав из файла %v символов\n", <-rsultOfrollingAction)
}(rsultOfrollingAction)
// читаем канал из входных аргументов somework
a := <-ch
// записываем в вызодной кнал
out <- a
// сигнализируем об окончании somesomework()
wait <- true
}()
fmt.Printf("прошло >>%v<< наносекунд с момента начала функции work и функция somework завершилась передав управление main\n", time.Now().UnixNano()-start)
// возвращаем канал с результататом работы somework
return out
}
// поростейшее чтение файла.
func readf(name string) []byte {
f, err := ioutil.ReadFile(name)
if err != nil {
fmt.Println(err)
os.Exit(123)
}
return f
}
Ответы (1 шт):
Первая проблема
В канал wait вы пишете не в той функции. Смотрите, что у вас происходит:
main порождает somework и продолжает работу. Затем останавливается, ожидая канал out.
somework порождает вложенную горутину и завершается.
Стартует первая анонимная горутина, которую рантайм Го обозначает как main.somework.func1. Она
- порождает анонимную горутину
main.somework.func1.1 - читает канал
in, записывает в каналoutи - ТАДАМ записывает в канал
wait
Где-то между 2 и 3 разблокируется main, читая из канала out. Затем main снова блокируется, ожидая из канала wait. И в тот же момент main снова пробуждается, так как горутина main.somework.func1 записала в wait.
main печатает сообщение и ЗАВЕРШАЕТСЯ. Есть ненулевая вероятность, что вторая анонимная горутина main.somework.func1.1 даже не успела стартануть.
Вам нужно перенести wait <- true в ту горутину, которая ожидает завершения чтения из файла:
go func() {
rsultOfrollingAction := make(chan int)
// далее запускаю длительную операцию чтения файла в отдельной горутине
go func(rsultOfrollingAction chan int) {
d := make(chan bool) //для сигнализации об окончании длительного действия
// начало длительной операции в отдельной горутине
// результат данной работы, при имеющемся коде, не выводится.
// ДАННЫЙ ВОПРОС ОТНОСТИИТСЯ КАК РАЗ К ЭТОЙ ПРОБЛЕМЕ
go func(d chan bool) {
time.Sleep(time.Second * 3)
text := readf("sample.bin")
rsultOfrollingAction <- len(text)
// сигнализируем горутине которая должна вывести количество
// прочитанных строк, что длительная операция завершене
d <- true
}(d)
// когда d получает сигнал об окончании длительной операции
<-d
// выводим результат длительной операции.
fmt.Printf("вложенная в somework горутина завершила выполенение прочитав из файла %v символов\n", <-rsultOfrollingAction)
wait <- true
}(rsultOfrollingAction)
// читаем канал из входных аргументов somework
a := <-in
// записываем в вызодной кнал
out <- a
// сигнализируем об окончании somesomework()
// wait <- true
}()
Видите? Я перенёс запись в wait из первой вложенной горутины во вторую, так как вторая горутина точно дождётся чтения из файла благодаря синхронизации через d и rsultOfrollingAction. Именно она должна сигнализировать, что длинная операция завершена.
Вторая проблема
При такой синхронизации образуется дедлок. Причина в том, что инструкция make(chan T) создаёт канал нулевой ёмкости. То есть читатель блокируется, если нет писателя, писатель блокируется, если нет читателя. Что и просиходит у вас:
- вторая анонимная функция ждёт канал
d(<-d). Так как в момент ожиданияdтретья горутина ещё не стартовала, то вторая горутина блокируется. - третья анонимная горутина ждёт, потом читает файл, потом записывает результат в
rsultOfrollingAction(rsultOfrollingAction <- len(text)). И ... тоже блокируется! Так как у каналаrsultOfrollingActionв этот момент ещё нет читателя. Читатель - вторая горутина - заблокирована на ожиданииd.
Что с этим можно сделать.
План А.
Выбросить d. Появление данных в rsultOfrollingAction само собой обозначает, что операция чтения завершена.
Пруф: https://go.dev/play/p/mXxkYgXkyxw
main дождается выполнения вложенных горутин.
План Б
Если канал d вам дорог сам по себе, то нужно сделать канал rsultOfrollingAction асинхронным: rsultOfrollingAction := make(chan int, 1)
Если канал пуст, писатель не будет ждать появления читателя. Записанное значение будет записано в буфер канала, и третья горутина благополучно перейдёт к следующей операции d <- true