猿问

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

我一直在研究 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()将其视为反模式?为什么在这种情况下通道不是大多数人首选的,因为它们可以用来保持所有代码异步和线程优化?


慕少森
浏览 132回答 1
1回答

ibeautiful

您对“阻塞”的理解不正确。诸如WaitGroup.Wait()或通道接收之类的阻塞操作(当没有要接收的值时)只会阻塞 goroutine 的执行,它们不会(必然)阻塞用于执行 goroutine(的语句)的 OS 线程。每当遇到阻塞操作(例如上面提到的)时,goroutine 调度程序可能(并且它会)切换到另一个可能继续运行的 goroutine。在调用期间没有(重要的)CPU 周期丢失WaitGroup.Wait(),如果有其他 goroutines 可能继续运行,它们将会。
随时随地看视频慕课网APP

相关分类

Go
我要回答