Golang阻止和非阻塞

时间:2016-03-20 10:09:51

标签: multithreading go nonblocking

我对Go如何处理非阻塞IO感到有些困惑。 API对我来说大多看起来都是同步的,当在Go上观看演示时,听到诸如"以及呼叫块"

之类的评论并不罕见

从文件或网络读取时,Go是否使用阻止IO? 或者是否有某种魔法在Go Goout内部使用时重写代码?

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

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

4 个答案:

答案 0 :(得分:30)

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

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

您可以在此处详细了解Go的内部工作原理:

https://morsmachine.dk/go-scheduler

答案 1 :(得分:18)

您应首先阅读@Not_a_Golfer答案以及他提供的链接,以了解如何安排goroutine。我的答案更像是北斗星潜入网络IO。我假设您了解Go如何实现协作式多任务处理。

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

文件IO只是系统调用。 Not_a_Golfer已经涵盖了这一点。 Go将使用真实OS线程在系统调用上等待,并在返回时取消阻止goroutine。 Here您可以看到Unix的文件read实现。

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

例如,当您在TCP套接字goroutine上调用read时,首先会尝试使用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。您可以看到here findrunnable函数,用于搜索可以运行的goroutine。它调用netpoll函数,它将返回可以恢复的goroutine。您可以找到kqueue herenetpoll实施。

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

答案 2 :(得分:0)

有些issuespull request可能会帮助您:)

它也许可以解决一些问题,例如

  1. golang何时会阻止IO操作?
  2. 为什么golang仅将async io用于socket而不是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 on regular files

答案 3 :(得分:-1)

Go会在执行IO或系统调用时阻止当前的goroutine,但是当发生这种情况时,允许运行另一个goroutine而不是阻塞的goroutine。较旧版本的Go一次只允许一个正在运行的goroutine,但从1.5开始,该数字已更改为可用的cpu核心数。 (runtime.GOMAXPROCS

您不必担心Go中的阻止。例如,标准库的http服务器在goroutine中执行处理函数。如果您在提供http请求时尝试读取文件,则会阻止,但如果在第一个请求被阻止时进入另一个请求,则将允许另一个goroutine运行并提供该请求。然后当第二个goroutine完成,并且第一个goroutine不再被阻塞时,它将被恢复(如果GOMAXPROCS> 1,如果有一个自由线程,则阻塞的goroutine可能会更早恢复)。

有关详细信息,请查看以下内容: