golang可以启动多线程来处理网络IO吗
Can golang launch multi-thread to handle networkd IO
我想开发一个软件来使用 GoLang 处理来自多个 tcp 连接的请求,并且 运行 在具有 10Gb-nic 的服务器上。
单核上的recv/send数据好像性能不够。所以我想在多个 cpu 核上实现该软件 recv/send 数据。
然后我做了一个简单的测试服务器来检查GoLang是否可以recv/send多个cpu核上的数据。它启动多个 (16) goroutines 以在同一个侦听器上启动 http 服务器,并使用 ab(Apache Benchmark) 作为客户端。
服务器启动后,我看到只有一个线程调用EpollWait,但是服务器启动了18个线程,当我启动ab测试时使用16个并发,但是服务器只占用一个核心。
所以问题是:在 GoLang 中有没有什么方法可以启动多个线程来处理来自多个 tcp 连接的数据 recv/send。或者我是否必须调用 syscall.EpollWait 来制作网络框架,然后自己完成?
服务器测试代码:
package main
import (
"io"
"log"
"net"
"net/http"
"runtime"
)
type HandlerFunction struct{}
func (self HandlerFunction) ServeHTTP(w http.ResponseWriter, req *http.Request) {
data := "Hello"
//fmt.Printf("data_len=%d\n", len(data))
io.WriteString(w, string(data))
}
func RoutineFunction(hs *http.Server, l net.Listener) {
runtime.LockOSThread()
err := hs.Serve(l)
if err != nil {
log.Fatalf("serve fail, err=[%s]", err)
}
}
func main() {
runtime.GOMAXPROCS(16)
l, err := net.Listen("tcp", "0.0.0.0:12345")
if err != nil {
log.Fatalf("listen fail, err=[%s]", err)
}
for i := 0; i < 15; i++ {
hs := http.Server{}
hs.Handler = HandlerFunction{}
go RoutineFunction(&hs, l)
}
hs := http.Server{}
hs.Handler = HandlerFunction{}
RoutineFunction(&hs, l)
}
不完全是。
Go 运行time(从 go1.5 开始)使用单个网络轮询器。当您在服务器中完成实际工作时,这很少会成为瓶颈,线程 运行ning goroutines 将保持忙碌。但在某些情况下,无论是有足够的核心,还是有足够的吞吐量,Go 运行时间都会开始受到影响,特别是因为轮询器通常与执行 IO 的线程位于不同的 NUMA 节点中。
如果您需要 运行 这种规模,我目前建议将 Go 服务器限制为单个 NUMA 节点,并 运行 连接服务器的多个实例。
例外情况是,如果您将套接字置于阻塞模式,则该套接字上的 IO 将绑定到单个 OS 线程。我还没有对这种方法进行任何吞吐量测试以查看是否有任何好处,但如果您同时使用相对较少的套接字,尝试一下也没什么坏处。
我想开发一个软件来使用 GoLang 处理来自多个 tcp 连接的请求,并且 运行 在具有 10Gb-nic 的服务器上。
单核上的recv/send数据好像性能不够。所以我想在多个 cpu 核上实现该软件 recv/send 数据。
然后我做了一个简单的测试服务器来检查GoLang是否可以recv/send多个cpu核上的数据。它启动多个 (16) goroutines 以在同一个侦听器上启动 http 服务器,并使用 ab(Apache Benchmark) 作为客户端。
服务器启动后,我看到只有一个线程调用EpollWait,但是服务器启动了18个线程,当我启动ab测试时使用16个并发,但是服务器只占用一个核心。
所以问题是:在 GoLang 中有没有什么方法可以启动多个线程来处理来自多个 tcp 连接的数据 recv/send。或者我是否必须调用 syscall.EpollWait 来制作网络框架,然后自己完成?
服务器测试代码:
package main
import (
"io"
"log"
"net"
"net/http"
"runtime"
)
type HandlerFunction struct{}
func (self HandlerFunction) ServeHTTP(w http.ResponseWriter, req *http.Request) {
data := "Hello"
//fmt.Printf("data_len=%d\n", len(data))
io.WriteString(w, string(data))
}
func RoutineFunction(hs *http.Server, l net.Listener) {
runtime.LockOSThread()
err := hs.Serve(l)
if err != nil {
log.Fatalf("serve fail, err=[%s]", err)
}
}
func main() {
runtime.GOMAXPROCS(16)
l, err := net.Listen("tcp", "0.0.0.0:12345")
if err != nil {
log.Fatalf("listen fail, err=[%s]", err)
}
for i := 0; i < 15; i++ {
hs := http.Server{}
hs.Handler = HandlerFunction{}
go RoutineFunction(&hs, l)
}
hs := http.Server{}
hs.Handler = HandlerFunction{}
RoutineFunction(&hs, l)
}
不完全是。
Go 运行time(从 go1.5 开始)使用单个网络轮询器。当您在服务器中完成实际工作时,这很少会成为瓶颈,线程 运行ning goroutines 将保持忙碌。但在某些情况下,无论是有足够的核心,还是有足够的吞吐量,Go 运行时间都会开始受到影响,特别是因为轮询器通常与执行 IO 的线程位于不同的 NUMA 节点中。
如果您需要 运行 这种规模,我目前建议将 Go 服务器限制为单个 NUMA 节点,并 运行 连接服务器的多个实例。
例外情况是,如果您将套接字置于阻塞模式,则该套接字上的 IO 将绑定到单个 OS 线程。我还没有对这种方法进行任何吞吐量测试以查看是否有任何好处,但如果您同时使用相对较少的套接字,尝试一下也没什么坏处。