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

Golang 阻塞与非阻塞

Golang 阻塞与非阻塞

Go
大话西游666 2021-12-27 15:04:06
我对 Go 如何处理非阻塞 IO 感到有些困惑。API 在我看来大多是同步的,在 Go 上观看演示时,听到诸如“和调用块”之类的评论并不少见从文件或网络读取时,Go 是否使用阻塞 IO?或者在 Go Routine 中使用时是否有某种魔法可以重写代码?来自 C# 背景,这感觉非常不直观,在 C# 中,我们await在使用异步 API 时有关键字。这清楚地表明 API 可以产生当前线程并稍后在延续中继续。所以 TLDR; 在 Go 例程中执行 IO 时,Go 会阻塞当前线程,还是会使用延续将其转换为 C# 之类的 async await 状态机?
查看完整描述

3 回答

?
忽然笑

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

Go 有一个调度程序,可让您编写同步代码,并自行进行上下文切换并在后台使用异步 IO。因此,如果您正在运行多个 goroutine,它们可能会在单个系统线程上运行,并且当您的代码从 goroutine 的角度来看被阻塞时,它并不是真正的阻塞。这不是魔术,但是是的,它掩盖了您的所有这些东西。

调度程序将在需要时以及在真正阻塞的操作期间分配系统线程(例如,我认为文件 IO 正在阻塞,或调用 C 代码)。但是如果你正在做一些简单的 http 服务器,你实际上可以使用一些“真实线程”来拥有成千上万个 goroutine。

您可以在此处阅读有关 Go 内部工作原理的更多信息:

https://morsmachine.dk/go-scheduler


查看完整回答
反对 回复 2021-12-27
?
慕妹3242003

TA贡献1824条经验 获得超6个赞

您应该先阅读@Not_a_Golfer 的回答以及他提供的链接,以了解 goroutine 的调度方式。我的回答更像是更深入地研究网络 IO。我假设您了解 Go 如何实现协作式多任务处理。


Go 可以并且确实只使用阻塞调用,因为一切都在 goroutines 中运行,它们不是真正的 OS 线程。它们是绿线。因此,您可以让它们中的许多都阻塞 IO 调用,并且它们不会像操作系统线程那样占用您的所有内存和 CPU。


文件 IO 只是系统调用。Not_a_Golfer 已经涵盖了这一点。Go 将使用真正的操作系统线程来等待系统调用,并在 goroutine 返回时解除阻塞。在这里你可以看到readUnix 的文件实现。


网络IO不同。运行时使用“网络轮询器”来确定哪个 goroutine 应该从 IO 调用中解除阻塞。根据目标操作系统,它将使用可用的异步 API 来等待网络 IO 事件。调用看起来像阻塞,但内部的一切都是异步完成的。


例如,当您首先调用readTCP 套接字时,goroutine 将尝试使用 syscall 进行读取。如果还没有到达,它将阻塞并等待它恢复。在这里阻塞是指停放,它将 goroutine 放入等待恢复的队列中。这就是当您使用网络 IO 时“阻塞”goroutine 将执行交给其他 goroutine 的方式。


func (fd *netFD) Read(p []byte) (n int, err error) {

    if err := fd.readLock(); err != nil {

        return 0, err

    }

    defer fd.readUnlock()

    if err := fd.pd.PrepareRead(); err != nil {

        return 0, err

    }

    for {

        n, err = syscall.Read(fd.sysfd, p)

        if err != nil {

            n = 0

            if err == syscall.EAGAIN {

                if err = fd.pd.WaitRead(); err == nil {

                    continue

                }

            }

        }

        err = fd.eofError(n, err)

        break

    }

    if _, ok := err.(syscall.Errno); ok {

        err = os.NewSyscallError("read", err)

    }

    return

}

https://golang.org/src/net/fd_unix.go?s=#L237


当数据到达时,网络轮询器将返回应该恢复的 goroutine。您可以在此处 findrunnable看到搜索可以运行的 goroutine 的函数。它调用netpoll函数,该函数将返回可以恢复的 goroutine。你可以kqueue在netpoll 这里找到实现。


至于 C# 中的异步/等待。异步网络 IO 还将使用异步 API(Windows 上的 IO 完成端口)。当有东西到达时,操作系统将在线程池的完成端口线程之一上执行回调,这将继续在当前SynchronizationContext. 从某种意义上说,有一些相似之处(停放/取消停放确实看起来像调用延续,但在低得多的级别上)但是这些模型非常不同,更不用说实现了。默认情况下,Goroutines 不绑定到特定的 OS 线程,它们可以在其中任何一个线程上恢复,这无关紧要。没有需要处理的 UI 线程。Async/await 专门用于使用以下命令在同一 OS 线程上恢复工作SynchronizationContext. 并且因为没有绿色线程或单独的调度程序 async/await 必须将您的函数拆分为多个回调,这些回调SynchronizationContext基本上是一个无限循环,用于检查应执行的回调队列。你甚至可以自己实现它,这真的很容易。


查看完整回答
反对 回复 2021-12-27
?
慕的地8271018

TA贡献1796条经验 获得超4个赞

有一些issuespull request可能会帮助你:)

它可能会解决一些问题,例如

  1. golang什么时候会阻塞IO操作?

  2. 为什么 golang 只使用async ioforsocket而不是normal file

    https://github.com/golang/go/issues/18507 https://github.com/golang/go/commit/c05b06a12d005f50e4776095a60d6bd9c2c91fac https://github.com/golang/go/issues/6222 https://开头的github .com/golang/go/issues/6817 常规文件上的 Epoll


查看完整回答
反对 回复 2021-12-27
  • 3 回答
  • 0 关注
  • 474 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号