【问题标题】:Goroutine I/O schedulingGoroutine I/O 调度
【发布时间】:2015-08-05 11:09:19
【问题描述】:

Golang 的 goroutine 为 goroutine (-programmer) 提供了一个阻塞 I/O 的接口。在幕后,运行时自然会使用某种非阻塞 I/O 来防止 OS 挂起 OS 线程,以便运行时可以在执行 I/O 时在 OS 线程之上运行另一个 goroutine。

运行时何时考虑执行的 I/O 以便重新调度 goroutine?

为了清楚起见,假设我有一个 net.TCPConn,我调用了 Write,我什么时候可以期望 goroutine 被重新安排?

conn, err := net.Dial("tcp", serverAddr)
conn.Write(buffer)
timestamp = time.Now()

那是什么时候可以使用时间戳?

  • 缓冲区何时被复制到 golang 运行时?
  • 缓冲区何时被复制到运行时和操作系统的内核空间?
  • 缓冲区已复制到运行时、内核空间以及 NIC 的发送缓冲区时?
  • 缓冲区何时通过网络/从 NIC 发送?
  • 缓冲区何时被接收端 TCP 堆栈确认?

【问题讨论】:

  • 这个article 可能有点过时,但可以很好地了解调度程序的工作原理。
  • 完美!带我到morsmachine.dk/netpoller,它给了我答案。

标签: multithreading go io scheduling


【解决方案1】:

您可以查看文件https://github.com/golang/go/blob/master/src/net/fd_unix.go(写入函数)。

基本上,这取决于套接字缓冲区是否有足够的空间。

如果套接字缓冲区中有足够的空间来容纳您的写入操作的大小,则数据将立即写入套接字缓冲区。我想这对应于你的第二个答案。此外,内核可能实际发送数据包(或将其添加到 NIC 队列),但它独立于 Go 运行时。

如果套接字缓冲区中没有足够的空间来容纳整个写入操作,则只有部分数据会立即写入套接字缓冲区。然后,调用将阻塞(通过运行时轮询引擎),直到内核在套接字缓冲区中腾出一些空间(通过发送一些数据包)。只要有一些空间可用,并且所有数据都已复制,调用就会解除阻塞。

您应该考虑当 net 包通过系统调用将整个缓冲区写入套接字缓冲区时采用时间戳。

【讨论】:

    【解决方案2】:

    following article 描述了 netpoller 的工作原理:

    每当 goroutine 尝试读取或写入连接时,网络代码都会执行操作,直到收到这样的错误,然后调用 netpoller,告诉它在准备好执行 I/O 时通知 goroutine再次。然后将 goroutine 调度到它正在运行的线程之外,并在其位置运行另一个 goroutine。

    当 netpoller 收到来自操作系统的通知说它可以对文件描述符执行 I/O 时,它会查看其内部数据结构,查看是否有任何 goroutine 在该文件上被阻塞,如果有则通知它们任何。然后,goroutine 可以重试导致它阻塞的 I/O 操作并成功执行此操作。”

    因此我们得出结论,只要底层系统调用完成对整个缓冲区的写入,就可以重新调度 goroutine。在 Linux 的情况下,当消息被复制到内核空间发送缓冲区时,它似乎是蜜蜂:Blocking sockets: when, exactly, does "send()" return?。这又是我的第二个原始选项“当缓冲区被复制到运行时和操作系统的内核空间时”;也符合Didier Spezia's的回答。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-03
      • 2014-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多