我一直在研究 Golang,并通过其创新的 goroutines 构造来了解它的并发性以及它的仅协程通道模型的实施有多好。
我立即发现麻烦的一件事是该方法的使用Wait()
,该方法用于等待父 goroutine 内产生的多个未完成的 goroutine 完成。引用Golang 文档
等待可用于阻塞,直到所有 goroutine 完成
事实上,许多 Go 开发人员将其规定 Wait()
为实现并发的首选方式,这似乎与 Golang 使开发人员能够编写高效软件的使命背道而驰,因为阻塞是低效的,而真正的异步代码永远不会阻塞。
阻塞的进程 [或线程] 是等待某个事件的进程,例如资源变得可用或 I/O 操作完成。
换句话说,阻塞的线程将花费 CPU 周期做无用的事情,只是反复检查当前运行的任务是否可以停止等待并继续执行。
在真正的异步代码中,当协程遇到无法继续执行直到结果到达的情况时,它必须将其执行交给调度程序而不是阻塞,方法是将其状态从running切换到waiting,以便调度程序可以开始执行下一个 -来自可运行队列的内联协程。只有当它需要的结果到达时,等待协程才应该将其状态从等待更改为可运行。
因此,由于Wait()
阻塞直到 x 数量的 goroutine 被调用Done()
,调用的 goroutineWait()
将始终保持在可运行或运行状态,浪费 CPU 周期并依赖调度程序抢占长时间运行的 goroutine 只是将其状态从运行更改为可运行,而不是将其更改为应有的等待状态。
如果这一切都是真的,并且我理解如何Wait()
正确工作,那么为什么人们不使用内置的 Go 通道来完成等待子 goroutines 完成的任务?如果我理解正确,发送到缓冲通道和从任何通道读取都是异步操作,这意味着调用它们会使 goroutine 进入等待状态,那么为什么它们不是首选方法呢?
我引用的文章给出了几个例子。以下是作者所说的“老派”方式:
package main
import (
"fmt"
"time"
)
func main() {
messages := make(chan int)
go func() {
time.Sleep(time.Second * 3)
messages <- 1
}()
go func() {
time.Sleep(time.Second * 2)
messages <- 2
}()
go func() {
time.Sleep(time.Second * 1)
messages <- 3
}()
for i := 0; i < 3; i++ {
fmt.Println(<-messages)
}
}
这是首选的“规范”方式:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
messages := make(chan int)
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
time.Sleep(time.Second * 3)
messages <- 1
}()
go func() {
defer wg.Done()
time.Sleep(time.Second * 2)
messages <- 2
}()
go func() {
defer wg.Done()
time.Sleep(time.Second * 1)
messages <- 3
}()
wg.Wait()
for i := range messages {
fmt.Println(i)
}
}
我可以理解第二个可能比第一个更容易理解,但第一个是异步的,没有协程阻塞,第二个有一个阻塞的协程:运行主函数的协程。这是另一个Wait()被普遍接受的方法的例子。
如果创建低效的阻塞线程,为什么 Go 社区不Wait()将其视为反模式?为什么在这种情况下通道不是大多数人首选的,因为它们可以用来保持所有代码异步和线程优化?
ibeautiful
相关分类