Goroutine I/O 调度
Goroutine I/O scheduling
Golangs goroutines 向 goroutine(程序员)提供了一个阻塞接口 I/O。在幕后,运行time 自然地使用某种非阻塞 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 运行的时候?
- 当缓冲区被复制到运行的时候和OS的内核space?
- 当缓冲区已经被复制到运行时候,内核space并且另外复制到网卡的发送缓冲区?
- 缓冲区何时通过 network/from NIC 发送?
- 缓冲区何时被接收端 TCP 堆栈确认?
你可以看看文件https://github.com/golang/go/blob/master/src/net/fd_unix.go(写函数)。
基本上,这取决于套接字缓冲区是否足够space。
如果套接字缓冲区中有足够的 space 来容纳您的写操作的大小,数据将立即写入套接字缓冲区。我想这对应于您的第二个答案。此外,内核可能会实际发送数据包(或将其添加到 NIC 队列),但它独立于 Go 运行时。
如果socket buffer中没有足够的space来容纳整个写操作,只有部分数据会立即写入socket buffer。然后,调用将阻塞(通过运行时轮询引擎),直到内核在套接字缓冲区中创建了一些 space(通过发送一些数据包)。一旦一些 space 可用,并且所有数据都已复制,调用将解除阻塞。
您应该考虑在网络包通过系统调用将整个缓冲区写入套接字缓冲区时获取时间戳。
following article 描述了 netpoller 的工作原理:
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."
因此我们得出结论,只要底层系统调用完成对整个缓冲区的写入,就可以重新调度 goroutine。在 Linux 的情况下,当消息已被复制到内核时,似乎是蜜蜂 space 发送缓冲区: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.
Golangs goroutines 向 goroutine(程序员)提供了一个阻塞接口 I/O。在幕后,运行time 自然地使用某种非阻塞 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 运行的时候?
- 当缓冲区被复制到运行的时候和OS的内核space?
- 当缓冲区已经被复制到运行时候,内核space并且另外复制到网卡的发送缓冲区?
- 缓冲区何时通过 network/from NIC 发送?
- 缓冲区何时被接收端 TCP 堆栈确认?
你可以看看文件https://github.com/golang/go/blob/master/src/net/fd_unix.go(写函数)。
基本上,这取决于套接字缓冲区是否足够space。
如果套接字缓冲区中有足够的 space 来容纳您的写操作的大小,数据将立即写入套接字缓冲区。我想这对应于您的第二个答案。此外,内核可能会实际发送数据包(或将其添加到 NIC 队列),但它独立于 Go 运行时。
如果socket buffer中没有足够的space来容纳整个写操作,只有部分数据会立即写入socket buffer。然后,调用将阻塞(通过运行时轮询引擎),直到内核在套接字缓冲区中创建了一些 space(通过发送一些数据包)。一旦一些 space 可用,并且所有数据都已复制,调用将解除阻塞。
您应该考虑在网络包通过系统调用将整个缓冲区写入套接字缓冲区时获取时间戳。
following article 描述了 netpoller 的工作原理:
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."
因此我们得出结论,只要底层系统调用完成对整个缓冲区的写入,就可以重新调度 goroutine。在 Linux 的情况下,当消息已被复制到内核时,似乎是蜜蜂 space 发送缓冲区: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.