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

Aja*_*ung 1 concurrency channel go blocking coroutine

我一直在研究Golang,看看它通过其创新的goroutines构造实现了一个仅限协程通道的模型,它的并发性有多好.

我立即发现令人不安的一件事是使用该Wait()方法,用于等待父母goroutine中产生的多个突出的goroutine已经完成.引用Golang文档

等待可以用来阻止所有goroutines完成

许多开发人员规定 Wait()作为实现并发性的首选方式的事实似乎与Golang使开发人员编写高效软件的使命相对立,因为阻塞效率低下,真正的异步代码永远不会阻塞.

被阻止的进程[或线程]是等待某个事件的进程[或线程],例如资源变为可用或完成I/O操作.

换句话说,被阻塞的线程将花费CPU周期无用,只需反复检查以查看其当前正在运行的任务是否可以停止等待并继续执行.

真正的异步代码中,当协同程序遇到无法继续直到结果到达的情况时,它必须通过将其状态从运行切换到等待来将其执行发送到调度程序而不是阻塞,因此调度程序可以开始执行下一个 -来自可运行队列的内联协程.只有在需要的结果到达时,等待协程才应将其状态从等待运行变为可运行.

因此,由于Wait()直到x个goroutine被调用的块,调用Done()的goroutine Wait()将始终保持在可运行或运行状态,浪费CPU周期并依赖调度程序抢占长时间运行的goroutine,仅将其状态从运行更改为可运行,而不是将其更改为等待它应该是.

如果这一切都是真的,并且我理解如何Wait()正常工作,那么为什么人们不使用内置的Go通道来完成等待子goroutine的任务呢?如果我理解正确,发送到缓冲通道,并从任何通道读取都是异步操作,这意味着调用它们会使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)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是首选的"规范"方式:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以理解,第二个可能比第一个更容易理解,但第一个是异步,其中没有协同程序阻塞,第二个有一个coroutine阻塞:运行main函数的协同程序.是另一个Wait()普遍接受的方法的例子.

Wait()如果Go社区创建了一个效率低下的阻塞线程,为什么不将其视为反模式呢?在这种情况下,为什么大多数通道都不是首选,因为它们可以用来保持所有代码的异步和线程优化?

icz*_*cza 14

您对"阻止"的理解不正确.阻塞操作,例如WaitGroup.Wait()通道接收(当没有值接收时)仅阻止goroutine的执行,它们(必然)不会阻塞用于执行goroutine(的语句)的OS线程.

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

请检查相关问题:Go运行时使用的线程数

  • @AjaxLeung:"根据我的经验[阻止]总是参考OS线程." - 这不是术语"阻塞"的问题,它只是之前没有使用过这个执行模型的结果. (3认同)
  • 我认为阻塞似乎是一个错误的词,因为根据我的经验,它总是与操作系统线程相关。无论如何,“WaitGroup.Wait()”和通道的操作是否会向 Go 调度程序接收信号以立即停止这些 Goroutine 的执行,或者它们只会由于抢占式超时而停止,从而限制了 Goroutine 可以运行的时间? (2认同)
  • @AjaxLeung在Go世界中,几乎所有语句都与goroutine有关,而不是线程.如果有人说某些东西是阻塞的,那就意味着它阻止了goroutine - 除非明确说明. (2认同)
  • @AjaxLeung`Wait()`是一个很好的调度点,但它不能保证其他goroutine将被安排.这取决于调度程序.例如,如果所有`Done()`调用都在调用`Wait()`之前,那么`Wait()`将不会阻塞,并且goroutine可能会继续(没有调度程序调度另一个goroutine). (2认同)