【问题标题】:GO: Exceeds 10000-thread on simple tcp server?GO:在简单的 tcp 服务器上超过 10000 线程?
【发布时间】:2015-10-11 08:30:51
【问题描述】:

我需要编写一个可以处理超过 500k 连接的 tcp 服务器。

我在golang上写了一个简单的服务器,但是当连接数超过10k时,服务器崩溃了,报错“runtime: program beyond 10000-thread limit fatal error: thread exhaustion”。

服务器在最后一个 linux 版本中运行。那 go 可以使用 epoll 进行 tcp 连接,使其异步并使用少量 fd。那么为什么服务器会超出线程限制呢?!

我的简单服务器:

package main
import (
    "strconv"
    "net"
    "log"
    "time"
    "bufio"
    "io"
)

type Handler struct {
    conn   net.Conn
    closed chan bool
}

func (h *Handler) Listen() { // listen connection for incomming data
    defer h.conn.Close()
    bf := bufio.NewReader(h.conn)
    for {
        line, _, err := bf.ReadLine()
        if err != nil {
            if err == io.EOF {
                log.Println("End connection")
            }
            h.closed <- true // send to dispatcher, that connection is closed
            return
        }

        // ... some business logic with data
    }
}

type Dispatcher struct {
    handlers map[string]*Handler `map[ip]*Handler`
}

func (d *Dispatcher) AddHandler(conn net.Conn) {
    addr := conn.RemoteAddr().String()
    handler := &Handler{conn, make(chan bool, 1)}
    d.handlers[addr] = handler

    go handler.Listen()

    <-handler.closed // when connection closed, remove handler from handlers
    delete(d.handlers, addr)
}

func (d *Dispatcher) ListenHandlers(port int) {
    sport := strconv.Itoa(port)

    ln, err := net.Listen("tcp", ":" + sport)
    if err != nil {
        log.Println(err)
        return
    }

    defer ln.Close()

    for {
        conn, err := ln.Accept() // accept connection
        if err != nil {
            log.Println(err)
            continue
        }

        tcpconn := conn.(*net.TCPConn)
        tcpconn.SetKeepAlive(true)
        tcpconn.SetKeepAlivePeriod(10 * time.Second)

        go d.AddHandler(conn)
    }
}

func main() {
    dispatcher := &Dispatcher{make(map[string]*Handler)}
    dispatcher.ListenHandlers(3000)
}

11.10.2015 更新
问题是https://github.com/felixge/tcpkeepalive 库。请不要使用它:)

【问题讨论】:

  • 每当一个 goroutine 进行阻塞系统调用或去调用库中的 C 代码时,go 都会为其他 goroutine 使用一个新线程。我怀疑注释为 // ... some business logic with data 的部分有一些阻塞导致 go 旋转额外的 goroutine。您是否有机会在那部分调用一些 C 代码?
  • @sintanial:Go 的 net 包将使用 epoll/select 对多个套接字进行多路读取和写入。您是否尝试过使用 gdb 检查应用程序以查看看到此错误时所有线程在做什么?
  • TCP 连接在每个对等点上使用一个端口。端口由 16 位数字表示,因此您对单个 IP 地址上可以拥有的 TCP 连接数有技术限制,这与 0.5M 连接的目标相去甚远。你想做什么?
  • @UlrichEckhardt 来自多个源地址和端口的 AFAIK,您可以拥有超过 65k 个连接,因为每个连接都有两个由它标识的地址。
  • @Not_a_Golfer 你是对的。我删除了我的业务逻辑,并运行服务器,它工作正常。在 34 个线程中运行,仅此而已,线程没有增加:)。谢谢你,我认为在golang问题中:)

标签: multithreading tcp go


【解决方案1】:

问题在于https://github.com/felixge/tcpkeepalive 库。 该库获取套接字文件描述符(fd),然后复制并使用以阻塞模式运行的新 fd。因此,在每个连接上创建新线程。 Go 不要杀死创建的线程(因为这是本机 golang 行为),并且在某些时候失败并出现“超过 10000 个线程”。而已 ! 请不要使用它:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-30
    • 2021-05-04
    • 2022-01-25
    • 1970-01-01
    • 2011-12-17
    • 1970-01-01
    • 2012-06-15
    • 2012-11-27
    相关资源
    最近更新 更多