为了账号安全,请及时绑定邮箱和手机立即绑定

如果 sync.WaitGroup 类型的 Wait() 方法阻塞,因此不是异步的,为什么要使用它?

如果 sync.WaitGroup 类型的 Wait() 方法阻塞,因此不是异步的,为什么要使用它?

Go
慕少森 2023-03-29 17:26:33
我一直在研究 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 mainimport (    "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 mainimport (    "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()将其视为反模式?为什么在这种情况下通道不是大多数人首选的,因为它们可以用来保持所有代码异步和线程优化?
查看完整描述

1 回答

?
ibeautiful

TA贡献1993条经验 获得超5个赞

您对“阻塞”的理解不正确。诸如WaitGroup.Wait()或通道接收之类的阻塞操作(当没有要接收的值时)只会阻塞 goroutine 的执行,它们不会(必然)阻塞用于执行 goroutine(的语句)的 OS 线程。

每当遇到阻塞操作(例如上面提到的)时,goroutine 调度程序可能(并且它会)切换到另一个可能继续运行的 goroutine。在调用期间没有(重要的)CPU 周期丢失WaitGroup.Wait(),如果有其他 goroutines 可能继续运行,它们将会。


查看完整回答
反对 回复 2023-03-29
  • 1 回答
  • 0 关注
  • 131 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信