英文:
Goroutine I/O scheduling
问题
Golang的goroutines提供了一个阻塞I/O的接口给goroutine(程序员)。在幕后,运行时自然会使用某种非阻塞I/O来防止操作系统挂起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堆栈确认接收时?
英文:
Golangs goroutines present an interface of blocking I/O to the goroutine (-programmer). Behind the scenes the runtime naturally uses some kind of non-blocking I/O to prevent the OS from suspending the OS-thread, so that the runtime can run another goroutine on top of the OS thread while the I/O is performed.
When does the runtime consider the I/O performed so that it can reschedule the goroutine?
To make it clear, assuming I have a net.TCPConn
that I call Write
on, when can I expect the goroutine to be rescheduled?
conn, err := net.Dial("tcp", serverAddr)
conn.Write(buffer)
timestamp = time.Now()
That is when can I expect the timestamp to be taken?
- When the buffer has been copied to the golang runtime?
- When the buffer has been copied to the runtime and to the OS's kernel space?
- When the buffer has been copied to the runtime, kernel space and additionally to the NIC's send buffer?
- When the buffer has been sent over the network/from the NIC?
- When the buffer has been acknowledged by the recieve ends TCP stack?
答案1
得分: 4
你可以查看文件https://github.com/golang/go/blob/master/src/net/fd_unix.go(Write函数)。
基本上,这取决于套接字缓冲区是否有足够的空间。
如果套接字缓冲区有足够的空间来容纳你的写操作的大小,数据将立即写入套接字缓冲区。我猜这对应于你的第二个答案。此外,内核可能实际发送数据包(或将其添加到NIC队列),但这与Go运行时无关。
如果套接字缓冲区没有足够的空间来容纳整个写操作,只有部分数据将立即写入套接字缓冲区。然后,调用将阻塞(通过运行时轮询引擎),直到内核在套接字缓冲区中腾出一些空间(通过发送一些数据包)。一旦有一些空间可用,并且所有数据都已复制,调用将解除阻塞。
你应该考虑时间戳是在net包通过系统调用将整个缓冲区写入套接字缓冲区时获取的。
英文:
You can have a look in file https://github.com/golang/go/blob/master/src/net/fd_unix.go (Write function).
Basically, it depends whether the socket buffer has enough space or not.
If there is enough space in the socket buffer to accommodate the size of your write operation, the data will be immediately written to the socket buffer. I guess this corresponds to your second answer. Additionally, the kernel may actually send the packet (or add it to the NIC queues), but it is independent from the Go runtime.
If there is not enough space in the socket buffer to accommodate the whole write operation, only part of the data will be immediately written to the socket buffer. Then, the call will block (via the runtime polling engine) until the kernel has made some space in the socket buffer (by sending some packets). As soon as some space is available, and all the data have been copied, the call will unblock.
You should consider the timestamp is taken when the net package has written the whole buffer in the socket buffer via a system call.
答案2
得分: 2
以下是翻译好的内容:
以下文章描述了netpoller的工作原理:
每当一个goroutine尝试读取或写入连接时,网络代码将执行该操作,直到收到错误,然后调用netpoller,告诉它在准备好再次执行I/O时通知该goroutine。然后,该goroutine将从其所在的线程中调度出来,另一个goroutine将代替它运行。
当netpoller收到来自操作系统的通知,表示可以在文件描述符上执行I/O操作时,它将查看其内部数据结构,看是否有任何被阻塞在该文件上的goroutine,并在有时通知它们。然后,goroutine可以重试导致其阻塞的I/O操作,并成功执行。
因此,我们可以得出结论,只要底层系统调用完成对整个缓冲区的写入,goroutine就可以重新调度。在Linux的情况下,似乎是在消息被复制到内核空间发送缓冲区时:https://stackoverflow.com/questions/5407182/blocking-sockets-when-exactly-does-send-return。这也是我的第二个原始选项:“当缓冲区已被复制到运行时和操作系统的内核空间时”;这也与Didier Spezia的答案一致。
英文:
The following article describes how the netpoller works:
>Whenever a goroutine tries to read or write to a connection, the networking code will do the operation until it receives such an error, then call into the netpoller, telling it to notify the goroutine when it is ready to perform I/O again. The goroutine is then scheduled out of the thread it's running on and another goroutine is run in its place.
>
> When the netpoller receives notification from the OS that it can perform I/O on a file descriptor, it will look through its internal data structure, see if there are any goroutines that are blocked on that file and notify them if there are any. The goroutine can then retry the I/O operation that caused it to block and succeed in doing so."
Thus we conclude that the goroutine can be rescheduled whenever the underlying system call completes writing for the entire buffer. In the case of Linux, it seems to bee when the message has been copied to the kernel space send buffer: https://stackoverflow.com/questions/5407182/blocking-sockets-when-exactly-does-send-return. Which in turn is my second original option "When the buffer has been copied to the runtime and to the OS's kernel space"; also consistent with Didier Spezia's answer.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论