multithreading Golang 阻塞与非阻塞

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36112445/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 22:40:19  来源:igfitidea点击:

Golang blocking and non blocking

multithreadinggononblocking

提问by Roger Johansson

I am somewhat confused over how Go handles non blocking IO. API's mostly look synchronous to me, and when watching presentations on Go, its not uncommon to hear comments like "and the call blocks"

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

Is Go using blocking IO when reading from files or network? Or is there some kind of magic that re-writes the code when used from inside a Go Routine?

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

Coming from a C# background, this feels very non intuitive, in C# we have the awaitkeyword when consuming async API's. Which clearly communicates that the API can yield the current thread and continue later inside a continuation.

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

So TLDR; Will Go block the current thread when doing IO inside a Go routine, or will it be transformed into a C# like async await state machine using continuations?

所以 TLDR; 在 Go 例程中执行 IO 时,Go 会阻塞当前线程,还是会使用延续将其转换为 C# 之类的 async await 状态机?

采纳答案by Not_a_Golfer

Go has a scheduler that lets you write synchronous code, and does context switching on its own and uses async IO under the hood. So if you're running several goroutines, they might run on a single system thread, and when your code is blocking from the goroutine's view, it's not really blocking. It's not magic, but yes, it masks all this stuff from you.

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

The scheduler will allocate system threads when they're needed, and during operations that are really blocking (I think file IO is blocking for example, or calling C code). But if you're doing some simple http server, you can have thousands and thousands of goroutine using actually a handful of "real threads".

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

You can read more about the inner workings of Go here:

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

https://morsmachine.dk/go-scheduler

https://morsmachine.dk/go-scheduler

回答by creker

You should read @Not_a_Golfer answer first and the link he provided to understand how goroutines are scheduled. My answer is more like a deeper dive into network IO specifically. I assume you understand how Go achieves cooperative multitasking.

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

Go can and does use only blocking calls because everything runs in goroutines and they're not real OS threads. They're green threads. So you can have many of them all blocking on IO calls and they will not eat all of your memory and CPU like OS threads would.

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

File IO is just syscalls. Not_a_Golfer already covered that. Go will use real OS thread to wait on a syscall and will unblock the goroutine when it returns. Hereyou can see file readimplementation for Unix.

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

Network IO is different. The runtime uses "network poller" to determine which goroutine should unblock from IO call. Depending on the target OS it will use available asynchronous APIs to wait for network IO events. Calls look like blocking but inside everything is done asynchronously.

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

For example, when you call readon TCP socket goroutine first will try to read using syscall. If nothing is arrived yet it will block and wait for it to be resumed. By blocking here I mean parking which puts the goroutine in a queue where it awaits resuming. That's how "blocked" goroutine yields execution to other goroutines when you use network 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

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

When data arrives network poller will return goroutines that should be resumed. You can see herefindrunnablefunction that searches for goroutines that can be run. It calls netpollfunction which will return goroutines that can be resumed. You can find kqueueimplementation of netpollhere.

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

As for async/wait in C#. async network IO will also use asynchronous APIs (IO completion ports on Windows). When something arrives OS will execute callback on one of the threadpool's completion port threads which will put continuation on the current SynchronizationContext. In a sense, there are some similarities (parking/unparking does looks like calling continuations but on a much lower level) but these models are very different, not to mention the implementations. Goroutines by default are not bound to a specific OS thread, they can be resumed on any one of them, it doesn't matter. There're no UI threads to deal with. Async/await are specifically made for the purpose of resuming the work on the same OS thread using SynchronizationContext. And because there're no green threads or a separate scheduler async/await have to split your function into multiple callbacks that get executed on SynchronizationContextwhich is basically an infinite loop that checks a queue of callbacks that should be executed. You can even implement it yourself, it's really easy.

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

回答by toffee

There are some issuesand pull requestmay be help you :)

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

It maybe solve some questions like

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

  1. When golang will blocking IO operation?
  2. Why golang only use async iofor socketinstead of normal file?

    https://github.com/golang/go/issues/18507https://github.com/golang/go/commit/c05b06a12d005f50e4776095a60d6bd9c2c91fachttps://github.com/golang/go/issues/6222https://github.com/golang/go/issues/6817Epoll on regular files

  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