jjzjj

go - 为什么我的 golang 程序创建了这么多线程?

coder 2023-06-29 原文

我的服务器运行了一段时间,创建了大约 200 个连接并做了一些计算并关闭,我发现它占用了大约 2,7G 内存,并且在几天后没有减少。程序本身并没有占用那么多,而且我通过memstats查了一下。通过 cat/proc/11686/status | grep -i threads 我得到了 Threads: 177,所以我认为它占用这么多内存的原因是它创建了很多线程。为什么 go创建这么多线程?是因为我用了太多go func()吗?而且我确定 goroutines 没有增加并且它们正常退出。

附言

我的程序代码太多,所以我排除了细节,只保留主要内容

我的问题是当 go 创建一个线程来做某事时。有这么多线程正常吗?我认为它与代码无关。

主.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
}

最佳答案

使用Go,你可以创建很多goroutines,它不应该增加线程数。在您的代码中,运行 Go 代码的线程数受 runtime.NumCPU() 限制。

当 goroutine 必须执行阻塞调用时,例如系统调用,或通过 cgo 调用 C 库时,可能会创建线程。在这种情况下,运行时调度程序会从其调度池中删除运行 goroutine 的线程。如果调度池的线程少于 GOMAXPROCS,那么将创建一个新线程。

您可以在此处找到有关其工作原理的更多信息: https://softwareengineering.stackexchange.com/questions/222642/are-go-langs-goroutine-pools-just-green-threads/222694#222694

要了解代码生成线程的原因,您必须调查导致阻塞系统调用或 C 调用的所有代码路径。请注意,与网络相关的调用是非阻塞的,因为它们会被标准库自动多路复用。但是,如果你执行一些磁盘I/O,或者调用外部库,这将产生线程。

例如,您的代码中使用的日志记录库可能会执行一些阻塞 I/O,从而导致创建线程(特别是如果生成的文件托管在慢速设备上)。

关于go - 为什么我的 golang 程序创建了这么多线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27600587/

有关go - 为什么我的 golang 程序创建了这么多线程?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  3. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  5. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  6. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  7. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  8. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  9. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  10. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

随机推荐