我对Go如何处理非阻塞IO感到有点困惑。API在我看来大多是同步的,当在Go上观看演示文稿时,经常会听到像“和调用阻塞”这样的评论。
Go在读取文件或网络时是否使用阻塞IO?或者,当从Go例程内部使用时,是否有某种魔力可以重写代码?
来自C#背景,这感觉非常不直观,在C#中,我们在使用异步API时使用await关键字,这清楚地传达了API可以放弃当前线程并在以后继续进行的信息。
那么,在Go例程中执行IO时,TLDR;会阻塞当前线程,还是会使用continuations将其转换为类似于异步等待状态机的C#?
发布于 2016-03-20 18:23:58
Go有一个调度器,可以让你编写同步代码,自己进行上下文切换,并在幕后使用异步IO。因此,如果您正在运行多个goroutine,它们可能在单个系统线程上运行,并且当您的代码从goroutine的视图中阻塞时,它并不是真正的阻塞。它不是魔法,但它对你掩盖了所有这些东西。
调度器会在需要时分配系统线程,并在真正阻塞的操作期间(例如,我认为文件IO阻塞,或者调用C代码)。但是如果你正在做一些简单的http服务器,你可以有成千上万的goroutine,实际上使用的是一些“真正的线程”。
你可以在这里阅读更多关于Go内部工作原理的内容:
发布于 2016-03-21 02:23:08
您应该先阅读@Not_a_Golfer answer和他提供的链接,以了解如何调度goroutines。我的回答更像是更深入地研究网络IO。我假设您了解Go是如何实现协作多任务处理的。
Go可以并且确实只使用阻塞调用,因为一切都运行在goroutines中,而它们不是真正的OS线程。它们是绿色的线。所以你可以让它们中的许多都阻塞IO调用,并且它们不会像OS线程那样占用你所有的内存和CPU。
文件IO只是系统调用。Not_a_Golfer已经报道了这一点。Go将使用真实的操作系统线程来等待syscall,并在它返回时解除对goroutine的阻塞。你可以查看Unix下的文件read实现。
网络IO不同。运行时使用"network poller“来确定哪个goroutine应该解除对IO调用的阻塞。根据目标操作系统的不同,它将使用可用的异步API来等待网络IO事件。调用看起来像阻塞,但在内部一切都是异步完成的。
例如,当您在TCP socket上调用read时,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
当数据到达时,网络轮询器将返回应该恢复的goroutines。您可以看到搜索可以运行的goroutines的here findrunnable函数。它调用netpoll函数,该函数将返回可以恢复的goroutines。您可以找到netpoll here的kqueue实现。
至于C#中的异步/等待。异步网络IO也将使用异步API(Windows上的IO完成端口)。当有东西到达时,操作系统将在线程池的完成端口线程之一上执行回调,这将在当前SynchronizationContext上放置continuation。在某种意义上,有一些相似之处(停车/取消停车看起来像调用continuations,但在更低的级别上),但这些模型非常不同,更不用说实现了。默认情况下,Goroutines不绑定到特定的OS线程,它们可以在其中任何一个线程上恢复,这并不重要。没有要处理的UI线程。Async/await是专门为使用SynchronizationContext在同一操作系统线程上恢复工作而创建的。而且,由于没有绿色线程或单独的调度程序,async/await必须将函数拆分成多个在SynchronizationContext上执行的回调,这基本上是一个无限循环,它检查应该执行的回调队列。你甚至可以自己实现它,这真的很简单。
发布于 2019-09-16 14:52:11
有一些issues和pull request可能会对您有所帮助:)
它可能会解决一些问题,比如
socket使用async io而不使用normal filehttps://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 fileshttps://stackoverflow.com/questions/36112445
复制相似问题