首页 > 解决方案 > golang 通道内存使用是动态的吗?

问题描述

我测试了 go 通道内存使用情况,发现它与通道输入频率不同,而 goroutine 的数量相同。

正如下面的代码,我创建了数千个 goroutine,它们将数据生成到自己的通道并使用来自同一通道的数据。

通过仅更改生产者的变量“interval”,我可以通过使用命令“top”进行监视,看到虚拟内存和常驻内存也发生了变化。

而且间隔越短,内存的使用量就越大。

有谁知道发生了什么?

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

type Session struct {
    KeepAlive chan bool
}

var count = 1024 * 8 * 4

var 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.8

func 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")
}

标签: go

解决方案


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 的文档:

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)
    }
}

推荐阅读