我很难理解 goroutine 和通道。结果这个话题对我来说很难,有一瞬间困扰着我(因为我不明白)。我将提供完整的代码,因为我不确定问题是否出在 somework 函数中(尽管几乎肯定存在)。
这个问题完全是虚构的,要理解:
当其中一个嵌套 goroutine 正在执行长时间运行的操作时,如何继续执行所有 goroutine,包括 main...
我把我所有的想法写在代码的注释中,包括标记出对我提出问题的地方。因此,欢迎直接上代码。感谢您的关注和时间。
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
}
第一个问题
wait您正在错误的函数中写入通道。看看发生了什么:main生成somework并继续工作。然后它停下来,等待频道out。somework产生一个嵌套的 goroutine 并终止。第一个匿名 goroutine 启动,Go 运行时将其表示为
main.somework.func1。她main.somework.func1.1in、写入通道out以及wait2 和 3 之间的某处通过从通道读取来解锁
mainout。然后main它再次阻塞,从通道等待wait。与此同时,main它再次醒来,因为 goroutinemain.somework.func1写入了wait.main打印一条消息并结束。main.somework.func1.1第二个匿名 Goroutine甚至没有时间启动的概率非零。您需要转移
wait <- true到等待文件读取完成的 goroutine:你有看到?我将条目
wait从第一个嵌套 goroutine 移至第二个,因为由于通过d和进行同步,第二个 goroutine 肯定会等到它从文件中读取数据rsultOfrollingAction。应该是她发出漫长的手术结束的信号。第二个问题
这样的同步就形成了死锁。原因是该指令
make(chan T)创建了一个零容量的通道。即没有writer则reader被阻塞,没有reader则writer被阻塞。这就是你身上发生的事情:d(<-d)。由于等待时d第三个 Goroutine 尚未启动,因此第二个 Goroutine 被阻塞。rsultOfrollingAction(rsultOfrollingAction <- len(text))。而且……还被封锁了!由于该频道rsultOfrollingAction目前还没有读者。读取器(第二个 goroutine)在等待时被阻塞d。你能为这个做什么?
计划A。
把它扔掉
d。数据的出现rsultOfrollingAction本身就表明读取操作完成。证明: https: //go.dev/play/p/mXxkYgXkyxw
main等待嵌套 goroutine 完成。计划b
如果通道
d本身对你来说很重要,那么你需要使通道rsultOfrollingAction异步:rsultOfrollingAction := make(chan int, 1)如果通道是空的,写入者将不会等待读取者出现。写入的值将被写入通道缓冲区,第三个goroutine将安全地继续进行下一个操作
d <- true证明: https: //go.dev/play/p/4BeRqa2ilJT