代码之家  ›  专栏  ›  技术社区  ›  Roger Johansson

Golang阻塞和非阻塞

  •  54
  • Roger Johansson  · 技术社区  · 8 年前

    我对Go如何处理非阻塞IO有些困惑。 API在我看来大多是同步的,在Go上观看演示时,经常会听到“和调用块”之类的评论

    从文件或网络读取时,Go是否使用阻塞IO? 或者,在Go例程中使用时,是否有某种魔法可以重写代码?

    来自C#背景,这感觉非常不直观,在C#中我们有 await 使用异步API时使用关键字。 这清楚地表明,API可以生成当前线程,并在后续继续中继续。

    因此,TLDR; 在Go例程中执行IO时,Go会阻止当前线程,还是会使用continuations将其转换为类似C#的异步等待状态机?

    2 回复  |  直到 8 年前
        1
  •  55
  •   Not_a_Golfer    8 年前

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

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

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

    https://morsmachine.dk/go-scheduler

        2
  •  36
  •   Jonnny    5 年前

    你应该先阅读@Not_a_Golfer的答案和他提供的链接,以了解goroutines是如何安排的。我的回答更像是对网络IO的深入研究。我想你知道Go是如何实现协同多任务的。

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

    文件IO只是系统调用。高尔夫球手已经报道过了。Go将使用真正的OS线程来等待系统调用,并在返回时解锁goroutine。 Here 你可以看到文件 read Unix的实现。

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

    例如,当您呼叫 阅读 在TCP套接字上,goroutine将首先尝试使用syscall读取。如果还没有到达,它将阻塞并等待恢复。这里的“阻塞”是指停车,这会让高尔夫球队排队等待恢复。这就是当您使用网络IO时,“阻塞”goroutine如何让其他goroutines执行。

    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

    当数据到达时,网络轮询器将返回应该恢复的goroutines。你可以看到 here findrunnable 函数,用于搜索可以运行的goroutines。它叫 netpoll 函数,该函数将返回可恢复的goroutines。你可以找到 kqueue 实施 网络投票 here .

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