Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why my program of golang create so many threads?

Tags:

go

My server runned for a time and about 200 connection created and did some calculations and closed, I found that it took up about 2,7G memory and never decreased after serveral days. The program itself didn't occupy that much , And I checked it by memstats. by cat /proc/11686/status | grep -i threads I got Threads: 177,so I think the reason that it took up so much memory is that it created to many threads .Why go create so much threads? Is it because I use too many go func()? And I'm sure goroutines didn't increase and they exited normally.

PS

There is so many code in my program, so I exclude the details, just keep the main

And my problem is when go create a thread to do something. and is it normal to have so many thread? I think it is not concerned much to the code.

main.go

package main

import (
    "sanguo/base/log"
    "fmt"
    "runtime"
    "math/rand"
    "time"
    "net"
    "os"
)

type GameServer struct {
    Host   string
}


func (server *GameServer) Start() {
    // load system data
    log.Debug("/*************************SREVER START********************************/")

    tcpAddr, err := net.ResolveTCPAddr("tcp4", server.Host)
    if err != nil {
        log.Error(err.Error())
        os.Exit(-1)
    }
    go func(){
        for{
            select {
            case <-time.After(30*time.Second):
                LookUp("read memstats")
            }
        }
    }()
    listener, err := net.ListenTCP("tcp", tcpAddr)
    if err != nil {
        log.Error(err.Error())
        os.Exit(-1)
    }
    log.Debug("/*************************SERVER SUCC********************************/")
    for {
        conn, err := listener.AcceptTCP()
        if err != nil {
            continue
        }
        log.Debug("Accept a new connection ", conn.RemoteAddr())
        go handleClient(conn)
    }
}

func handleClient(conn *net.TCPConn) {
    sess := NewSession(conn)
    sess.Start()
}

func main() {
    rand.Seed(time.Now().Unix())

    runtime.GOMAXPROCS(runtime.NumCPU())

    log.SetLevel(0)

    filew := log.NewFileWriter("log", true)
    err := filew.StartLogger()
    if err != nil {
        fmt.Println("Failed start log",err)
        return
    }

    var server GameServer
    server.Host = "127.0.0.1:9999"
    server.Start()
}

session.go

package main

import (
    "io"
    "encoding/binary"
    "encoding/json"
    "github.com/felixge/tcpkeepalive"
    "net"
    "sanguo/base/log"
    "strings"
    "sync"
    "time"
)


type Session struct {

    conn *net.TCPConn //the tcp connection from client

    recvChan      chan *bufferedManager.Token //data from client
    closeNotiChan chan bool   //

    ok   bool
    lock sync.Mutex

}


func NewSession(connection *net.TCPConn) (sess *Session) {
    var client Session

    client.conn = connection

    client.recvChan = make(chan []byte, 1024)
    client.closeNotiChan = make(chan bool)
    client.ok = true

    log.Debug("New Connection", &client)

    kaConn, err := tcpkeepalive.EnableKeepAlive(connection)
    if err != nil {
        log.Debug("EnableKeepAlive err ", err)
    } else {
        kaConn.SetKeepAliveIdle(120 * time.Second)
        kaConn.SetKeepAliveCount(4)
        kaConn.SetKeepAliveInterval(5 * time.Second)
    }
    return &client
}


func (sess *Session) Close() {
    sess.lock.Lock()
    if sess.ok {
        sess.ok = false
        close(sess.closeNotiChan)
        sess.conn.Close()
        log.Trace("Sess Close Succ", sess, sess.uid)
    }
    sess.lock.Unlock()
}

func (sess *Session) handleRecv() {
    defer func(){
        if err := recover(); err != nil {
            log.Critical("Panic", err)
        }
        log.Trace("Session Recv Exit", sess, sess.uid)
        sess.Close()
    }()
    ch := sess.recvChan
    header := make([]byte, 2)
    for {
        /**block until recieve len(header)**/
        n, err := io.ReadFull(sess.conn, header)
        if n == 0 && err == io.EOF {
            //Opposite socket is closed
            log.Warn("Socket Read EOF And Close", sess)
            break
        } else if err != nil {
            //Sth wrong with this socket
            log.Warn("Socket Wrong:", err)
            break
        }
        size := binary.LittleEndian.Uint16(header) + 4
        data := make([]byte, size)
        n, err = io.ReadFull(sess.conn, t.Data)
        if n == 0 && err == io.EOF {
            log.Warn("Socket Read EOF And Close", sess)
            break
        } else if err != nil {
            log.Warn("Socket Wrong:", err)
            break
        }
        ch <- data //send data to Client to process
    }
}

func (sess *Session) handleDispatch() {
    defer func(){
        log.Trace("Session Dispatch Exit",  sess, sess.uid)
        sess.Close()
    }()
    for {
        select {
        case msg, _ := <-sess.recvChan:
            log.Debug("msg", msg)
            sess.SendDirectly("helloworldhellowor", 1)

        case <-sess.closeNotiChan:
                return
        }
    }
}

func (sess *Session) Start() {
    defer func() {
        if err := recover(); err != nil {
            log.Critical("Panic", err)
        }
    }()
    go sess.handleRecv()

    sess.handleDispatch()

    close(sess.recvChan)
    log.Warn("Session Start Exit", sess, sess.uid)
}


func (sess *Session) SendDirectly(back interface{}, op int) bool {
    back_json, err := json.Marshal(back)
    if err != nil {
        log.Error("Can't encode json message ", err, back)
        return false
    }
    log.Debug(sess.uid, "OUT cmd:", op, string(back_json))
    _, err = sess.conn.Write(back_json)
    if err != nil {
        log.Error("send fail", err)
        return false
    }
    return true
}
like image 268
frank.lin Avatar asked Mar 17 '23 13:03

frank.lin


1 Answers

With Go, you can create many goroutines, it should not increase the number of threads. In your code, the number of threads running Go code is capped by runtime.NumCPU().

A thread may be created when the goroutine has to perform a blocking call, such as a system call, or a call to a C library via cgo. In that case, the runtime scheduler removes the thread running the goroutine from its scheduling pool. If the scheduling pool has less threads than GOMAXPROCS, then a new one will be created.

You can find a bit more information about how it works here: https://softwareengineering.stackexchange.com/questions/222642/are-go-langs-goroutine-pools-just-green-threads/222694#222694

To understand why your code generates threads, you have to investigate all the code paths resulting in blocking system calls or C calls. Note that network related calls are non-blocking, since they are automatically multiplexed by the standard library. However, if you perform some disks I/Os, or call foreign libraries, this will generate threads.

For instance, the logging library used in your code may perform some blocking I/Os resulting in threads being created (especially if the generated files are hosted on a slow device).

like image 163
Didier Spezia Avatar answered Apr 02 '23 11:04

Didier Spezia