为了账号安全,请及时绑定邮箱和手机立即绑定

golang 通道内存使用是动态的吗?

golang 通道内存使用是动态的吗?

Go
有只小跳蛙 2023-05-22 15:56:51
我测试了 go channel 内存使用情况,发现它与 channel 输入频率不同,而 goroutines 的数量是相同的。正如下面的代码,我创建了数千个 goroutine,它们将数据生成到自己的通道并使用来自同一通道的数据。通过仅更改生产者的变量“间隔”,我可以通过使用命令“top”进行监视来看到虚拟内存和常驻内存也发生变化。并且间隔越短,内存的使用就越大。有谁知道会发生什么?package mainimport (    "fmt"    "os"    "os/signal"    "syscall"    "time")type Session struct {    KeepAlive chan bool}var count = 1024 * 8 * 4var interval = 250 * time.Millisecond //3718.0m 3.587g   1.2m S 224.0 23.1// var interval = 500 * time.Millisecond //2011.2m 1.923g   1.2m S 118.8 12.4// var interval = 1 * time.Second   //1124.0m 1.059g   1.1m S  73.0  6.8func main() {    var gracefulStop = make(chan os.Signal, 1)    signal.Notify(gracefulStop, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)    for i := 0; i < count; i++ {        go Loop()    }    <-gracefulStop    fmt.Println("gracefulStop")}func Loop() (err error) {    var se *Session    se = NewSession()    se.Serve()    return}func NewSession() (s *Session) {    fmt.Println("NewSession")    s = &Session{        KeepAlive: make(chan bool, 1),    }    return}func (s *Session) Serve() {    fmt.Println("Serve")    go s.sendLoop()    s.readLoop()    s.Close()    return}func (s *Session) Close() {    close(s.KeepAlive)    fmt.Println("Close")}// local-------------------------------------------------------func (s *Session) readLoop() {    fmt.Println("readLoop")    sec := time.Duration(1 * time.Minute)ServerHandlerLoop:    for {        select {        case alive := <-s.KeepAlive:            if alive == false {                break ServerHandlerLoop            }        case <-time.After(sec):            fmt.Println("Timeout")            break ServerHandlerLoop        }    }    fmt.Println("readLoop EXIT")}func (s *Session) sendLoop() {    for {        s.KeepAlive <- true        time.Sleep(interval)    }    s.KeepAlive <- false    fmt.Println("ReadMessage EXIT")}
查看完整描述

1 回答

?
青春有我

TA贡献1784条经验 获得超8个赞

pprof可以告诉你在哪里花费了内存。只需为net/http/pprof包添加一个导入语句,并使用 http.DefaultServeMux 启动一个 HTTP 服务器:

import _ "net/http/pprof"


func main() {

    go func() { log.Fatal(http.ListenAndServe(":4000", nil)) }()


    //...

}

在程序运行时,运行 pprof 工具来查看有关程序的各种统计信息。由于您关心内存使用情况,因此堆配置文件(使用中的内存)可能是最相关的。


$ go tool pprof -top 10 http://localhost:4000/debug/pprof/heap

Fetching profile over HTTP from http://localhost:4000/debug/pprof/heap

File: foo

Build ID: 10

Type: inuse_space

Time: Dec 21, 2018 at 12:52pm (CET)

Showing nodes accounting for 827.57MB, 99.62% of 830.73MB total

Dropped 9 nodes (cum <= 4.15MB)

      flat  flat%   sum%        cum   cum%

  778.56MB 93.72% 93.72%   796.31MB 95.86%  time.NewTimer

   18.25MB  2.20% 95.92%    18.25MB  2.20%  time.Sleep

   17.75MB  2.14% 98.05%    17.75MB  2.14%  time.startTimer

      11MB  1.32% 99.38%       11MB  1.32%  runtime.malg

       2MB  0.24% 99.62%   798.31MB 96.10%  main.(*Session).readLoop

         0     0% 99.62%   798.31MB 96.10%  main.(*Session).Serve

         0     0% 99.62%    18.25MB  2.20%  main.(*Session).sendLoop

         0     0% 99.62%   800.81MB 96.40%  main.Loop

         0     0% 99.62%    11.67MB  1.40%  runtime.mstart

         0     0% 99.62%    11.67MB  1.40%  runtime.newproc.func1

         0     0% 99.62%    11.67MB  1.40%  runtime.newproc1

         0     0% 99.62%    11.67MB  1.40%  runtime.systemstack

         0     0% 99.62%   796.31MB 95.86%  time.After

time.Timer不出所料,您创建的大量stime.After占用了几乎所有正在使用的内存。

想一想:使用 250 毫秒的时间间隔,您创建计时器的速度比使用 1 秒的时间间隔快 4 倍。然而,计时器的寿命与间隔不成比例——它恒定为 60 秒。因此,在任何给定时间点,您有 4*60=240 倍以上的计时器处于活动状态。

来自 time.After 的文档:

等待持续时间过去后,然后在返回的通道上发送当前时间。它等同于 NewTimer(d).C。在计时器触发之前,垃圾收集器不会回收底层计时器。如果效率是一个问题,请改用 NewTimer 并在不再需要计时器时调用 Timer.Stop。

因此,为每个创建一个计时器readLoop并重新使用它。您可以通过使用空结构值通道而不是布尔值通道来进一步减少内存使用:

type Session struct {

    KeepAlive chan struct{}

}


func (s *Session) readLoop() {

    fmt.Println("readLoop")


    d := 1 * time.Minute

    t := time.NewTimer(d)


loop:

    for {

        select {

        case _, ok := <-s.KeepAlive:

            if !ok {

                break loop

            }


            if !t.Stop() {

                <-t.C

            }

            t.Reset(d)


        case <-t.C:

            fmt.Println("Timeout")

            break loop

        }

    }


    fmt.Println("readLoop EXIT")

}


func (s *Session) sendLoop() {

    defer close(s.KeepAlive)


    for {

        s.KeepAlive <- struct{}{}

        time.Sleep(interval)

    }

}


查看完整回答
反对 回复 2023-05-22
  • 1 回答
  • 0 关注
  • 89 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信