乐趣区

关于后端:瞬间高并发goroutine结束后的资源占用问题

问题

「当 Go 零碎遭逢突增流量,洪峰过境,流量恢复正常后,整个零碎的资源耗费是否会变大?」

第一反馈,应该是会复原到之前的程度吧!资源耗费在流量恢复正常之后,为什么会变大呢 …

复现

模仿一下。

咱们晓得 Go 原生的网络模型goroutine-per-connection,即一个链接调配一个 goroutine 去解决。当流量突增,很有可能使协程数量也同步减少。(更多可参考 Go 原生网络模型)

即如:

本来失常流量情景下,有 1000 个 goroutine,但此时忽然来了大量申请,goroutine 数暴增至 100w。

可用如下代码模仿:

package main

import (
    "fmt"
    _ "net/http/pprof"
    "runtime"
    "time"
)

func main() {fmt.Printf("最后!程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    for i := 0; i < 1000000; i++ {go func() {time.Sleep(time.Second * 10)
        }()}

    fmt.Printf("for 循环完结后!程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    time.Sleep(5e9)

    fmt.Printf("5s 后程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    time.Sleep(5e9)

    fmt.Printf("10s 后程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    time.Sleep(5e9)
    fmt.Printf("15s 后程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    time.Sleep(5e9)
    fmt.Printf("20s 后程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    // 用于阻塞不使程序退出
    select {}

    // 或者用这种形式
    //    //ch := make(chan int, 0)
    //    //ch <- 1
}

执行后果如下:

最后!程序中 goroutine 的数量为:1
for 循环完结后!程序中 goroutine 的数量为:1000001
5s 后程序中 goroutine 的数量为:1000001
10s 后程序中 goroutine 的数量为:1
15s 后程序中 goroutine 的数量为:1
20s 后程序中 goroutine 的数量为:1
...

以后代码无从得悉在启动 100w 协程前后的内存 /CPU 等信息,减少局部代码,晋升资源层面的可观测性

package main

import (
    "fmt"
    "net/http"
    _ "net/http/pprof"
    "os"
    "runtime"
    "runtime/debug"
    "time"

    "github.com/arl/statsviz"
    "github.com/shirou/gopsutil/process"
)

// after: 该过程的 cpu 占用率:0.12448928050371065, 内存占用:0.047302246, 工夫:2023-06-15 18:40:16
// before: 该过程的 cpu 占用率:3.9694322415591414, 内存占用:13.763237, 工夫:2023-06-15 18:43:47

var DateTime = "2006-01-02 15:04:05"

// 晋升资源层面的可观测性

func main() {statsviz.RegisterDefault() // 实时查看 Go 利用程序运行时统计信息(GC,MemStats 等)

    // 打印 GC 信息
    go printGCStats()
    // pprof
    go func() {fmt.Println(http.ListenAndServe(fmt.Sprintf("127.0.0.1:%s", "6060"), nil))
    }()

    // 打印 cpu 和内存的应用信息
    go func() {pid := os.Getpid()
        fmt.Println("以后程序的过程号为:", pid)

        p, _ := process.NewProcess(int32(pid))
        for {v, _ := p.CPUPercent()
            if v > 0 {memPercent, _ := p.MemoryPercent()
                fmt.Printf("该过程的 cpu 占用率:%v, 内存占用:%v, 工夫:%v\n", v, memPercent, time.Now().Format(DateTime))
                println("--------------- 分割线 ------------------")
            }
            time.Sleep(5 * time.Second)
        }
    }()

    fmt.Printf("最后!程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    //for i := 0; i < 1000000; i++ {//    go func() {//        time.Sleep(time.Second * 10)
    //    }()
    //}

    time.Sleep(5e9)
    fmt.Printf("for 循环完结后!程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    time.Sleep(5e9)

    fmt.Printf("5s 后程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    time.Sleep(5e9)
    fmt.Printf("10s 后程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    time.Sleep(5e9)
    fmt.Printf("15s 后程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    time.Sleep(5e9)
    fmt.Printf("20s 后程序中 goroutine 的数量为:%d\n", runtime.NumGoroutine())

    // 用于阻塞不使程序退出
    select {}

    // 或者用这种形式
    //    //ch := make(chan int, 0)
    //    //ch <- 1
}

func printGCStats() {t := time.NewTicker(time.Second)
    s := debug.GCStats{}
    for {
        select {
        case <-t.C:
            debug.ReadGCStats(&s)
            fmt.Printf("gc %d last@%v, PauseTotal %v\n", s.NumGC, s.LastGC, s.PauseTotal)
        }
    }
}

和之前代码比,新增了 4 块代码,其中

  • statsviz.RegisterDefault() 是一个第三方的可视化工具(github.com/arl/statsviz),可能查看 Go 利用程序运行时统计信息(GC,MemStats 等)
  • printGCStats(), 每隔 1s 打印程序的 GC 信息
  • fmt.Println(http.ListenAndServe(fmt.Sprintf(“127.0.0.1:%s”, “6060”), nil)) 开启 pprof,这步在很多我的项目中十分常见,属惯例操作
  • p.CPUPercent() && p.MemoryPercent(), 借助 github.com/shirou/gopsutil 这个库,每隔 5s 打印一下以后程序的 CPU 和内存应用信息

将开启 100w 协程的代码正文,记录 ” 空跑 ” 的性能:

最后!程序中 goroutine 的数量为:4
以后程序的过程号为:95252
该过程的 cpu 占用率:0.8364421098415779, 内存占用:0.044441223, 工夫:2023-06-16 10:29:23
--------------- 分割线 ------------------
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
for 循环完结后!程序中 goroutine 的数量为:11
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
该过程的 cpu 占用率:0.6441600370520854, 内存占用:0.09536743, 工夫:2023-06-16 10:29:28
--------------- 分割线 ------------------
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
5s 后程序中 goroutine 的数量为:11
gc 0 last@1970-01-01 08:00:00 +0800 CST, PauseTotal 0s
该过程的 cpu 占用率:0.44540045197456263, 内存占用:0.10166168, 工夫:2023-06-16 10:29:33
--------------- 分割线 ------------------
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
10s 后程序中 goroutine 的数量为:11
该过程的 cpu 占用率:0.3693817952398507, 内存占用:0.10223389, 工夫:2023-06-16 10:29:38
--------------- 分割线 ------------------
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
15s 后程序中 goroutine 的数量为:11
该过程的 cpu 占用率:0.32912235386802935, 内存占用:0.102996826, 工夫:2023-06-16 10:29:43
--------------- 分割线 ------------------
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
20s 后程序中 goroutine 的数量为:11
该过程的 cpu 占用率:0.34234244240639955, 内存占用:0.10328293, 工夫:2023-06-16 10:29:48
--------------- 分割线 ------------------
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
该过程的 cpu 占用率:0.28744831080521077, 内存占用:0.10385513, 工夫:2023-06-16 10:29:53
--------------- 分割线 ------------------
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
gc 1 last@2023-06-16 10:29:33.227392 +0800 CST, PauseTotal 70.042µs
该过程的 cpu 占用率:0.27528844379029793, 内存占用:0.10404587, 工夫:2023-06-16 10:29:58

...

可见 GC 频次很低。CPU 应用大抵在 0.2~0.8,内存在 0.04~0.11

同时拜访 http://localhost:6060/debug/statsviz/

heap in-use 只有几 M

此时将开启 100w 协程,每个协程 sleep 5s(模仿业务解决)的代码勾销正文,再次运行:

最后!程序中 goroutine 的数量为:4
以后程序的过程号为:96105
该过程的 cpu 占用率:4.517216078449847, 内存占用:0.44116974, 工夫:2023-06-16 10:36:53
--------------- 分割线 ------------------
gc 7 last@2023-06-16 10:36:54.255362 +0800 CST, PauseTotal 8.854457ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:63.50660430793033, 内存占用:15.632629, 工夫:2023-06-16 10:36:58
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
for 循环完结后!程序中 goroutine 的数量为:1000004
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:37.10674171813685, 内存占用:15.63282, 工夫:2023-06-16 10:37:03
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
5s 后程序中 goroutine 的数量为:5
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:32.49715724959919, 内存占用:15.641212, 工夫:2023-06-16 10:37:08
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
10s 后程序中 goroutine 的数量为:5
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:24.971469749626166, 内存占用:14.190102, 工夫:2023-06-16 10:37:13
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
15s 后程序中 goroutine 的数量为:5
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:20.3450391244844, 内存占用:13.17606, 工夫:2023-06-16 10:37:18
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
20s 后程序中 goroutine 的数量为:5
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:17.15503649797085, 内存占用:12.8685, 工夫:2023-06-16 10:37:23
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:14.835354648135736, 内存占用:12.566948, 工夫:2023-06-16 10:37:28
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:13.075560238064284, 内存占用:12.267494, 工夫:2023-06-16 10:37:33
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:11.71420929097974, 内存占用:9.054565, 工夫:2023-06-16 10:37:38
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:10.579057654995502, 内存占用:7.7142715, 工夫:2023-06-16 10:37:43
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:9.662050852130408, 内存占用:7.715225, 工夫:2023-06-16 10:37:48
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:8.894093245091423, 内存占用:7.716179, 工夫:2023-06-16 10:37:53
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:8.241124350205666, 内存占用:6.7832947, 工夫:2023-06-16 10:37:58
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:7.692988065351502, 内存占用:6.7634583, 工夫:2023-06-16 10:38:03
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:7.203602945727484, 内存占用:3.948021, 工夫:2023-06-16 10:38:08
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:6.761852639726575, 内存占用:3.780079, 工夫:2023-06-16 10:38:13
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:6.382606190610497, 内存占用:3.671646, 工夫:2023-06-16 10:38:18
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:6.055940735976846, 内存占用:3.6726952, 工夫:2023-06-16 10:38:23
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:5.752774396590926, 内存占用:3.6736488, 工夫:2023-06-16 10:38:29
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:5.47945995030865, 内存占用:0.18644333, 工夫:2023-06-16 10:38:34
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:5.231086125367948, 内存占用:0.17595291, 工夫:2023-06-16 10:38:39
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:5.014285852070234, 内存占用:0.17576218, 工夫:2023-06-16 10:38:44
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:4.798984172890425, 内存占用:0.10681152, 工夫:2023-06-16 10:38:49
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
该过程的 cpu 占用率:4.6097949036744055, 内存占用:0.09908676, 工夫:2023-06-16 10:38:54
--------------- 分割线 ------------------
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 8 last@2023-06-16 10:36:54.928286 +0800 CST, PauseTotal 8.925248ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
该过程的 cpu 占用率:5.040456346089843, 内存占用:14.997578, 工夫:2023-06-16 10:38:59
--------------- 分割线 ------------------
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
该过程的 cpu 占用率:4.856690266348459, 内存占用:14.652634, 工夫:2023-06-16 10:39:04
--------------- 分割线 ------------------
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
该过程的 cpu 占用率:4.693492356239568, 内存占用:14.085388, 工夫:2023-06-16 10:39:09
--------------- 分割线 ------------------
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
该过程的 cpu 占用率:4.534741370763308, 内存占用:11.00502, 工夫:2023-06-16 10:39:14
--------------- 分割线 ------------------
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
该过程的 cpu 占用率:4.386747711219474, 内存占用:6.7001343, 工夫:2023-06-16 10:39:19
--------------- 分割线 ------------------
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
该过程的 cpu 占用率:4.242128730141223, 内存占用:6.6851616, 工夫:2023-06-16 10:39:24
--------------- 分割线 ------------------
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
该过程的 cpu 占用率:4.119332039884263, 内存占用:6.0071945, 工夫:2023-06-16 10:39:29
--------------- 分割线 ------------------
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
gc 9 last@2023-06-16 10:38:55.995177 +0800 CST, PauseTotal 9.118457ms
该过程的 cpu 占用率:3.997977651649899, 内存占用:6.0079575, 工夫:2023-06-16 10:39:34
...

循环完结并期待 5s 后,协程的数量曾经降了下来。

但之后 GC 的耗时明显增加,CPU 和内存应用更是大幅减少。

拜访 http://localhost:6060/debug/statsviz/

heap in-use 达到了几百 M

before: 该过程的 cpu 占用率:0.27528844379029793, 内存占用:0.10404587, 工夫:2023-06-16 10:29:58

after: 该过程的 cpu 占用率:3.997977651649899, 内存占用:6.0079575, 工夫:2023-06-16 10:39:34

随同工夫消失,CPU 和内存占用会有回落,但相比开启 100w 协程之前的资源耗费,也还是会多一些。

定位

先抓一下 heap 的 profile,看一下 heap in-use 这几百 M 都用在了哪里。

curl http://127.0.0.1:6060/debug/pprof/heap -o heap_info.out

go tool pprof --http :9091 heap_info.out

可见调用关系是 runtime.systemstack–>runtime.newproc func1–>runtime.newproc1–>runtime.malg

runtime/proc.go:


// Create a new g running fn.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
func newproc(fn *funcval) {gp := getg()
    pc := getcallerpc()
    systemstack(func() {newg := newproc1(fn, gp, pc)

        pp := getg().m.p.ptr()
        runqput(pp, newg, true)

        if mainStarted {wakep()
        }
    })
}

//-------- 分割线 ----------


// Create a new g in state _Grunnable, starting at fn. callerpc is the
// address of the go statement that created this. The caller is responsible
// for adding the new g to the scheduler.
func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
    if fn == nil {fatal("go of nil func value")
    }

    mp := acquirem() // disable preemption because we hold M and P in local vars.
    pp := mp.p.ptr()
    newg := gfget(pp)
    if newg == nil {newg = malg(stackMin)
        casgstatus(newg, _Gidle, _Gdead)
        allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
    }
    if newg.stack.hi == 0 {throw("newproc1: newg missing stack")
    }

    if readgstatus(newg) != _Gdead {throw("newproc1: new g is not Gdead")
    }

    totalSize := uintptr(4*goarch.PtrSize + sys.MinFrameSize) // extra space in case of reads slightly beyond frame
    totalSize = alignUp(totalSize, sys.StackAlign)
    sp := newg.stack.hi - totalSize
    spArg := sp
    if usesLR {
        // caller's LR
        *(*uintptr)(unsafe.Pointer(sp)) = 0
        prepGoExitFrame(sp)
        spArg += sys.MinFrameSize
    }

    memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
    newg.sched.sp = sp
    newg.stktopsp = sp
    newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
    newg.sched.g = guintptr(unsafe.Pointer(newg))
    gostartcallfn(&newg.sched, fn)
    newg.parentGoid = callergp.goid
    newg.gopc = callerpc
    newg.ancestors = saveAncestors(callergp)
    newg.startpc = fn.fn
    if isSystemGoroutine(newg, false) {sched.ngsys.Add(1)
    } else {
        // Only user goroutines inherit pprof labels.
        if mp.curg != nil {newg.labels = mp.curg.labels}
        if goroutineProfile.active {
            // A concurrent goroutine profile is running. It should include
            // exactly the set of goroutines that were alive when the goroutine
            // profiler first stopped the world. That does not include newg, so
            // mark it as not needing a profile before transitioning it from
            // _Gdead.
            newg.goroutineProfiled.Store(goroutineProfileSatisfied)
        }
    }
    // Track initial transition?
    newg.trackingSeq = uint8(fastrand())
    if newg.trackingSeq%gTrackingPeriod == 0 {newg.tracking = true}
    casgstatus(newg, _Gdead, _Grunnable)
    gcController.addScannableStack(pp, int64(newg.stack.hi-newg.stack.lo))

    if pp.goidcache == pp.goidcacheend {
        // Sched.goidgen is the last allocated id,
        // this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
        // At startup sched.goidgen=0, so main goroutine receives goid=1.
        pp.goidcache = sched.goidgen.Add(_GoidCacheBatch)
        pp.goidcache -= _GoidCacheBatch - 1
        pp.goidcacheend = pp.goidcache + _GoidCacheBatch
    }
    newg.goid = pp.goidcache
    pp.goidcache++
    if raceenabled {newg.racectx = racegostart(callerpc)
        if newg.labels != nil {
            // See note in proflabel.go on labelSync's role in synchronizing
            // with the reads in the signal handler.
            racereleasemergeg(newg, unsafe.Pointer(&labelSync))
        }
    }
    if traceEnabled() {traceGoCreate(newg, newg.startpc)
    }
    releasem(mp)

    return newg
}

//-------- 分割线 ----------


// Allocate a new g, with a stack big enough for stacksize bytes.
func malg(stacksize int32) *g {newg := new(g)
    if stacksize >= 0 {stacksize = round2(stackSystem + stacksize)
        systemstack(func() {newg.stack = stackalloc(uint32(stacksize))
        })
        newg.stackguard0 = newg.stack.lo + stackGuard
        newg.stackguard1 = ^uintptr(0)
        // Clear the bottom word of the stack. We record g
        // there on gsignal stack during VDSO on ARM and ARM64.
        *(*uintptr)(unsafe.Pointer(newg.stack.lo)) = 0
    }
    return newg
}

剖析

newproc1 中有这样一段:

    newg := gfget(pp)
    if newg == nil {newg = malg(stackMin)
        casgstatus(newg, _Gidle, _Gdead)
        allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
    }

失常在创立 goroutine 构造体时,会优先复用 g 和 sudog 构造体。但如果在以后 p 的 goroutine 闲暇列表 (gFree) 及全局的 goroutine 闲暇列表找不到可用的 g,则会创立一个新的 g 构造体。该 g 构造领会被 append 到 allgs 切片(是一个全局变量) 中:

func allgadd(gp *g) {if readgstatus(gp) == _Gidle {throw("allgadd: bad status Gidle")
    }

    lock(&allglock)
    allgs = append(allgs, gp)
    if &allgs[0] != allgptr {atomicstorep(unsafe.Pointer(&allgptr), unsafe.Pointer(&allgs[0]))
    }
    atomic.Storeuintptr(&allglen, uintptr(len(allgs)))
    unlock(&allglock)
}

//-------- 分割线 ----------


var (
// 其余变量
allgs    []*g)
allgs 是一个全局变量,定义在 proc.go 这个文件中。它是一个指向所有 G(goroutine)的切片的指针。G 是 Go 语言中的协程,是实现并发的根本单元。allgs 的作用是跟踪所有的 goroutine,用于调试和监控。具体来说,allgs 的作用有以下几个方面:Debugging:allgs 变量可用于调试 Go 程序。通过打印切片中的所有元素,能够查看以后正在运行的所有 goroutine 的堆栈跟踪信息,以及它们的状态、调度状况等信息。Garbage Collection(垃圾回收):垃圾回收器须要跟踪所有的 goroutine 以理解它们是否还在运行。allgs 变量在垃圾回收期间用于遍历所有的 goroutine,并标记它们的栈。Runtime Statistics(运行时统计):allgs 变量还用于收集对于运行时的统计信息。例如,能够计算运行时同时存在的最大 goroutine 数量、goroutine 数量的平均值等等。总之,allgs 变量在 Go 语言的运行时零碎中扮演着重要的角色,用于跟踪所有的 goroutine,为调试、垃圾回收和运行时统计等提供反对。来自:https://github.com/cuishuang/explain-source-code-by-chatgpt/blob/main/runtime/proc.go.md#allgs

proc.go:


forEachG 函数是 Go 语言运行时中的一部分,其作用是遍历所有沉闷的 Goroutine(也称为 G),并执行一个指定的函数,对于每个 G 而言,都会调用该函数。该函数能够被看做一个并发的迭代器,用于拜访运行时中的每个 Goroutine。此函数在一些场景中十分有用,例如在 Go 的 GC 过程中,须要暂停所有的 Goroutine,避免它们继续执行并烦扰 GC 的过程。在这种状况下,能够应用 forEachG 函数来实现对所有 Goroutine 的扫描,并暂停它们。其余一些场景中也能够应用该函数,例如在调试工具中,须要列出所有以后运行的 Goroutine,或者在监视系统中进行性能剖析时,须要统计所有 Goroutine 的状态等等。总之,forEachG 函数是 Go 语言运行时中的一个十分有用的工具,能够帮忙开发者更好地治理 Goroutine,从而进步应用程序的性能和可靠性。来自: https://github.com/cuishuang/explain-source-code-by-chatgpt/blob/main/runtime/proc.go.md#foreachg
// forEachG calls fn on every G from allgs.
//
// forEachG takes a lock to exclude concurrent addition of new Gs.
func forEachG(fn func(gp *g)) {lock(&allglock)
    for _, gp := range allgs {fn(gp)
    }
    unlock(&allglock)
}

这个 forEachG 次要在以下几个中央应用:

mgc.go:

// gcResetMarkState resets global state prior to marking (concurrent
// or STW) and resets the stack scan state of all Gs.
//
// This is safe to do without the world stopped because any Gs created
// during or after this will start out in the reset state.
//
// gcResetMarkState must be called on the system stack because it acquires
// the heap lock. See mheap for details.
//
//go:systemstack
func gcResetMarkState() {
    // This may be called during a concurrent phase, so lock to make sure
    // allgs doesn't change.
    forEachG(func(gp *g) {
        gp.gcscandone = false // set to true in gcphasework
        gp.gcAssistBytes = 0
    })

    // Clear page marks. This is just 1MB per 64GB of heap, so the
    // time here is pretty trivial.
    lock(&mheap_.lock)
    arenas := mheap_.allArenas
    unlock(&mheap_.lock)
    for _, ai := range arenas {ha := mheap_.arenas[ai.l1()][ai.l2()]
        for i := range ha.pageMarks {ha.pageMarks[i] = 0
        }
    }

    work.bytesMarked = 0
    work.initialHeapLive = gcController.heapLive.Load()}
gcResetMarkState 是在 Go 语言中垃圾回收器 (gc) 的运行过程中调用的一个函数,它的作用是重置垃圾回收器中与标记相干的状态,以便下一轮垃圾回收可能正确地进行。在 Go 语言中的垃圾回收器执行过程中,须要分为两个阶段: 标记阶段和打扫阶段。在标记阶段中,会从根对象登程,遍历所有可能达到的对象,并将这些对象标记为流动对象。在这个过程中,为了避免对象被屡次标记或者不被标记的状况呈现,须要记录一些状态,并在实现标记后进行清理。gcResetMarkState 函数就是负责重置这些状态的函数。具体来说,它会清理各种指针标记位,还会重置某些内存区域的状态,以避免垃圾回收器在下一轮回收时受到烦扰。总之,gcResetMarkState 函数是垃圾回收器中要害的重置函数之一,它确保垃圾回收器在下一轮运行前的状态是正确的,这样能力精确地找到所有的垃圾对象进行回收。来自: https://github.com/cuishuang/explain-source-code-by-chatgpt/blob/main/runtime/mgc.go.md#gcresetmarkstate

proc.go:

// Check for deadlock situation.
// The check is based on number of running M's, if 0 -> deadlock.
// sched.lock must be held.
func checkdead() {
// ...

    forEachG(func(gp *g) {if isSystemGoroutine(gp, false) {return}
        s := readgstatus(gp)
        switch s &^ _Gscan {
        case _Gwaiting,
            _Gpreempted:
            grunning++
        case _Grunnable,
            _Grunning,
            _Gsyscall:
            print("runtime: checkdead: find g", gp.goid, "in status", s, "\n")
            unlock(&sched.lock)
            throw("checkdead: runnable g")
        }
    })
  
// ...

}
checkdead 函数次要负责查看并清理曾经死亡的 Goroutine(Go 语言中的轻量级线程)。该函数是 Go 语言运行时零碎中调度器的一部分,用于确保应用 Go 语言编写程序时,不会呈现曾经死亡的 Goroutine 占用内存和其余资源的状况。具体来说,checkdead 函数会通过查看 schedule 队列中的 Goroutine 列表以及其余相干的数据结构,查找曾经标记为死亡或者曾经退出的 Goroutine,并将其从队列和其余数据结构中革除。同时,checkdead 函数会开释相干资源,比方堆栈内存等。在 Go 语言中,Goroutine 是一种轻量级的并发机制,能够帮忙程序编写者实现高效率、高并发的程序。然而在应用 Goroutine 时,如果程序中存在大量死亡的 Goroutine 没有失去清理,程序的性能和稳定性将会受到重大影响。checkdead 函数的作用就是确保程序中曾经死亡的 Goroutine 失去及时清理,保障程序的性能和稳定性。来自: https://github.com/cuishuang/explain-source-code-by-chatgpt/blob/main/runtime/proc.go.md#checkdead
func schedtrace(detailed bool) {
  //...
      forEachG(func(gp *g) {print("G", gp.goid, ": status=", readgstatus(gp), "(", gp.waitreason.String(), ") m=")
        if gp.m != nil {print(gp.m.id)
        } else {print("nil")
        }
        print("lockedm=")
        if lockedm := gp.lockedm.ptr(); lockedm != nil {print(lockedm.id)
        } else {print("nil")
        }
        print("\n")
    })
// ...

}
schedtrace 是一个用于调试的函数,它能够用于跟踪调度器的行为。当开启 schedtrace 时,调度器会在每次调用时发送相干信息到调用方提供的函数中。这些信息包含调度器中各个 P(处理器)和 G(goroutine)的运行状况,以及调整调度器行为的各种事件。具体来说,schedtrace 有以下作用:跟踪 P 和 G 的状态:schedtrace 能够提供 P 的数量、运行的 G 的数量、G 的状态(running、runnable、waiting 等)等信息。这些信息能够帮忙调试过程中的调度问题,比方一个被阻塞的 G 是否存在,是否有足够的 P 来进行调度等等。发送事件信息:schedtrace 能够捕捉调度器中各种事件的触发,比方调整 P 的数量、调整 G 的优先级等等。通过 schedtrace 提供的事件信息,能够更深刻地理解调度器的运行机制、判断调度器是否失常工作,以及查找调度问题。提供调试接口:schedtrace 函数提供了一个调试接口,能够与调度器的其它调试工具(如 gdb)联合应用,帮忙调试调度问题。综上,schedtrace 是一个有着很重要作用的调试工具,开发人员能够通过这个工具更轻松地跟踪调度器的运行状况和调试调度问题。来自:https://github.com/cuishuang/explain-source-code-by-chatgpt/blob/main/runtime/proc.go.md#schedtrace

allgs 会在 GC 及查看死锁 (以及用 schedtrace 调试) 时用到,进行加锁遍历。

而这个切片只可能单向扩增,而没有膨胀操作。

当流量复原,这个在洪峰期间扩增的 allgs 切片,不会相应变小,还是要进行遍历扫描,从而 cpu 占用升高,用于解决业务的 cpu 占比相应缩小。

验证

想要获知 allgs 的长度变动,思考过用 dlv 或 eBPF,一番折腾都有一些坑。

间接罗唆在 runtime 中新增一个办法,如下:

func PrintAllgSize() int {return len(allgs)
}

执行 all.bash 从新编译源代码

package main

import (
    "fmt"
    _ "net/http/pprof"
    "runtime"
    "time"
)

func main() {fmt.Printf("最后!程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())
    //
    for i := 0; i < 1000000; i++ {go func() {time.Sleep(time.Second * 10)
        }()}

    time.Sleep(5e9)
    fmt.Printf("for 循环完结后!程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)

    fmt.Printf("5s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("10s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("15s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("20s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    for i := 0; i < 50000; i++ {go func() {time.Sleep(time.Second * 10)
        }()}

    time.Sleep(5e9)

    fmt.Printf("25s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("30s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    // 用于阻塞不使程序退出
    select {}}

输入:

最后!程序中 goroutine 的数量为:1,allg 大小为 6
for 循环完结后!程序中 goroutine 的数量为:1000001,allg 大小为 1000014
5s 后程序中 goroutine 的数量为:842287,allg 大小为 1000014
10s 后程序中 goroutine 的数量为:1,allg 大小为 1000014
15s 后程序中 goroutine 的数量为:1,allg 大小为 1000014
20s 后程序中 goroutine 的数量为:1,allg 大小为 1000014
25s 后程序中 goroutine 的数量为:50001,allg 大小为 1000014
30s 后程序中 goroutine 的数量为:1,allg 大小为 1000014

而如果将第一个 for 中 启协程的数量从 100w 改为 1w,则输入如下:

最后!程序中 goroutine 的数量为:1,allg 大小为 6
for 循环完结后!程序中 goroutine 的数量为:10001,allg 大小为 10014
5s 后程序中 goroutine 的数量为:1,allg 大小为 10014
10s 后程序中 goroutine 的数量为:1,allg 大小为 10014
15s 后程序中 goroutine 的数量为:1,allg 大小为 10014
20s 后程序中 goroutine 的数量为:1,allg 大小为 10014
25s 后程序中 goroutine 的数量为:50001,allg 大小为 50131
30s 后程序中 goroutine 的数量为:1,allg 大小为 50131

https://note.youdao.com/web/#/file/WEB484390d4553124b1472af71…

对系统压测影响有点大,每次压测都得重启一次服务。

发压力度差不多,然而 cpu usage 会变高,因为有些全局扫描会遍历 allgs。
狐疑是这个起因,具体还得剖析 pprof

最近在看大量 goroutine 的回收问题:如果我有一个 10 万 url 的切片,都是失常状况下申请会超时的外网,设置超时工夫 10s。而后遍历切片进行程序申请。依据 Go 原生的网络模型,这时会有大量的 goroutine。我的问题是,即使等最初一个 url 被申请完并超过 10s 后,大量的 goroutine 数量还是降不下来。有啥好的方法疾速让 goroutine 降下来吗(优先不引入第三方库如 reactor 模型网络库和池化包)

症状: 协程执行完结后,协程数量会恢复正常,内存却复原不了

goroutine 数量降下来了 … 陈年老问题,始终没解决。。能够看一下曹大提的 issue https://github.com/golang/go/issues/34457

其实 allgs 素来没有缩小过,不利于稳固,应该提供缩小的策略,比方 sysmon 监控发现 g 死了一半以上,就开释掉。

你的察看是正确的。目前运行时从不开释为 goroutines 创立的 g 对象,只管它的确重用它们。其次要起因是调度器常常在没有写屏障的状况下操作 g 指针(许多调度器代码在没有 P 的状况下运行,因而不能有写屏障),这使得很难确定何时能够对 g 进行垃圾收集。

大抵起因就是 go 的 gc 采纳的是并发垃圾回收,调度器在操作协程指针的时候不应用写屏障(draveness: 7.2 垃圾收集器),因为调度器在很多执行的时候须要应用 P(GPM),因而不能应用写屏障,所以调度器很难确定一个协程是否能够当成垃圾回收,这样调度器里的协程指针信息就会泄露。

xargin- 为什么 Go 模块在上游服务抖动复原后,CPU 占用无奈复原

保留 heap,goroutine,cpu 指标:

curl http://127.0.0.1:6060/debug/pprof/heap -o heap_cui.out && curl http://127.0.0.1:6060/debug/pprof/goroutine -o goroutine_cui.out && curl http://127.0.0.1:6060/debug/pprof/profile?seconds=30 -o profile_cui.out

go tool pprof heap_cui.out,而后命令行输出web;或间接go tool pprof --http :9091 heap_cui.out(这种形式好一些,信息更全,有上方的各种选项; 前一种形式只能看到一张图)

allgs contains all Gs ever created (including dead Gs), and thus never shrinks. Access via the slice is protected by allglock or stop-the-world. Readers that cannot take the lock may (carefully!) use the atomic variables below.

allgs 蕴含所有已经创立的 G(包含死 G),因而永不膨胀。通过切片的拜访受到 allglock 或 stop-the-world 的爱护。无奈获取锁的读者能够(小心!)应用上面的原子变量。

golang 中的 allgs 是什么作用

在 Go 语言的运行时零碎中,allgs 是一个全局变量,它是一个保留所有 goroutine 信息的切片。这个切片中蕴含所有以后正在运行或者阻塞的 goroutine 的信息,包含它们的状态、调用栈、所属线程等等。

allgs 变量在调试和监控程序中十分有用。例如,在程序解体时,能够应用 allgs 变量获取以后所有运行的 goroutine 的调用栈信息,以便更好地诊断问题。另外,allgs 变量还能够用于性能剖析和调试中,通过剖析所有 goroutine 的状态和调用栈信息,找出程序中可能存在的瓶颈和问题。

瞬間高併發 goroutine 結束回收問題,拜访卡顿,备份地址

Runtime: 当一个 goroutine 运行完结后会产生什么

goroutine 栈的申请与开释

https://medium.com/@openmohan/go-scheduling-and-garbage-colle…

https://blog.csdn.net/wuyuhao13579/article/details/109079570

什么都不起:

package main

import (
    "fmt"
    "net/http"
    _ "net/http/pprof"
    "os"
    "runtime"
    "time"

    "github.com/shirou/gopsutil/process"
)

func main() {go func() {fmt.Println(http.ListenAndServe(fmt.Sprintf("127.0.0.1:%s", "6060"), nil))
    }()

    go func() {pid := os.Getpid()
        fmt.Println("以后程序的过程号为:", pid)

        p, _ := process.NewProcess(int32(pid))
        for {v, _ := p.CPUPercent()
            //if v != 0.0 {
            if v > 0 {memPercent, _ := p.MemoryPercent()
                fmt.Printf("该过程的 cpu 占用率:%v, 内存占用:%v, 工夫:%v\n", v, memPercent, time.Now().Format(time.DateTime))
                println("--------------- 分割线 ------------------")
            }
            time.Sleep(1 * time.Second)
        }
    }()

    fmt.Printf("最后!程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    //for i := 0; i < 1000000; i++ {//    go func() {//        time.Sleep(time.Second * 10)
    //    }()
    //}

    time.Sleep(5e9)
    fmt.Printf("最初!程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)

    fmt.Printf("5s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("10s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("15s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("20s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    //for i := 0; i < 50000; i++ {//    go func() {//        time.Sleep(time.Second * 10)
    //    }()
    //}

    time.Sleep(5e9)

    fmt.Printf("25s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("30s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    // 用于阻塞不使程序退出
    select {}

    // 或者用这种形式
    //    //ch := make(chan int, 0)
    //    //ch <- 1
}

输入:

最后!程序中 goroutine 的数量为:3,allg 大小为 8
以后程序的过程号为:40990
该过程的 cpu 占用率:1.7345767936174512, 内存占用:0.044059753, 工夫:2023-05-12 18:56:11
--------------- 分割线 ------------------
该过程的 cpu 占用率:1.3833037997972075, 内存占用:0.046539307, 工夫:2023-05-12 18:56:12
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.9426338194391203, 内存占用:0.046920776, 工夫:2023-05-12 18:56:13
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.7145472045365172, 内存占用:0.047397614, 工夫:2023-05-12 18:56:14
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.7671273650057209, 内存占用:0.047683716, 工夫:2023-05-12 18:56:15
--------------- 分割线 ------------------
最初!程序中 goroutine 的数量为:3,allg 大小为 14
该过程的 cpu 占用率:0.6422326060530744, 内存占用:0.048160553, 工夫:2023-05-12 18:56:16
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.6902944326649534, 内存占用:0.048732758, 工夫:2023-05-12 18:56:17
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.6052315488807393, 内存占用:0.049114227, 工夫:2023-05-12 18:56:18
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5390835579514824, 内存占用:0.049591064, 工夫:2023-05-12 18:56:19
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5830942889704509, 内存占用:0.0500679, 工夫:2023-05-12 18:56:20
--------------- 分割线 ------------------
5s 后程序中 goroutine 的数量为:3,allg 大小为 18
该过程的 cpu 占用率:0.5306985682548677, 内存占用:0.05044937, 工夫:2023-05-12 18:56:21
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.6493627721946108, 内存占用:0.05083084, 工夫:2023-05-12 18:56:22
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5996871132486625, 内存占用:0.05121231, 工夫:2023-05-12 18:56:23
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5572058522494853, 内存占用:0.051403046, 工夫:2023-05-12 18:56:24
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5854899584894127, 内存占用:0.052261353, 工夫:2023-05-12 18:56:25
--------------- 分割线 ------------------
10s 后程序中 goroutine 的数量为:3,allg 大小为 21
该过程的 cpu 占用率:0.5490830861544309, 内存占用:0.05273819, 工夫:2023-05-12 18:56:26
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5744579673126523, 内存占用:0.05311966, 工夫:2023-05-12 18:56:27
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5427606369144101, 内存占用:0.05350113, 工夫:2023-05-12 18:56:28
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5658751753441396, 内存占用:0.05378723, 工夫:2023-05-12 18:56:29
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5377159332759053, 内存占用:0.054359436, 工夫:2023-05-12 18:56:30
--------------- 分割线 ------------------
15s 后程序中 goroutine 的数量为:3,allg 大小为 22
该过程的 cpu 占用率:0.5124016095745688, 内存占用:0.05455017, 工夫:2023-05-12 18:56:31
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5337136458170394, 内存占用:0.055408478, 工夫:2023-05-12 18:56:32
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5106376025360306, 内存占用:0.055789948, 工夫:2023-05-12 18:56:33
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5302615796688247, 内存占用:0.056171417, 工夫:2023-05-12 18:56:34
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5483911301025054, 内存占用:0.056648254, 工夫:2023-05-12 18:56:35
--------------- 分割线 ------------------
20s 后程序中 goroutine 的数量为:3,allg 大小为 29
该过程的 cpu 占用率:0.5274668569968027, 内存占用:0.057029724, 工夫:2023-05-12 18:56:36
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5442865856107826, 内存占用:0.057411194, 工夫:2023-05-12 18:56:37
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.524964879849538, 内存占用:0.0579834, 工夫:2023-05-12 18:56:38
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5069267142132382, 内存占用:0.05836487, 工夫:2023-05-12 18:56:39
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5227532433163139, 内存占用:0.058937073, 工夫:2023-05-12 18:56:40
--------------- 分割线 ------------------
25s 后程序中 goroutine 的数量为:3,allg 大小为 29
该过程的 cpu 占用率:0.5059914927017842, 内存占用:0.059223175, 工夫:2023-05-12 18:56:41
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.49014727393871627, 内存占用:0.05941391, 工夫:2023-05-12 18:56:42
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5051594610126843, 内存占用:0.059700012, 工夫:2023-05-12 18:56:43
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.49038278587960654, 内存占用:0.06017685, 工夫:2023-05-12 18:56:44
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.47644295091723954, 内存占用:0.060653687, 工夫:2023-05-12 18:56:45
--------------- 分割线 ------------------
30s 后程序中 goroutine 的数量为:3,allg 大小为 31
该过程的 cpu 占用率:0.517790279294169, 内存占用:0.06093979, 工夫:2023-05-12 18:56:46
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5038525093233929, 内存占用:0.061511993, 工夫:2023-05-12 18:56:47
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.516403412383421, 内存占用:0.0626564, 工夫:2023-05-12 18:56:48
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5032129771179247, 内存占用:0.06313324, 工夫:2023-05-12 18:56:49
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4906540582966204, 内存占用:0.06341934, 工夫:2023-05-12 18:56:50
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5026863919849385, 内存占用:0.06380081, 工夫:2023-05-12 18:56:51
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4907492710795917, 内存占用:0.06418228, 工夫:2023-05-12 18:56:52
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.47936986146895827, 内存占用:0.06465912, 工夫:2023-05-12 18:56:53
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4908517395484445, 内存占用:0.065135956, 工夫:2023-05-12 18:56:54
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.47997178114997224, 内存占用:0.06532669, 工夫:2023-05-12 18:56:55
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.49093380797098485, 内存占用:0.070381165, 工夫:2023-05-12 18:56:56
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5014240861901205, 内存占用:0.070381165, 工夫:2023-05-12 18:56:57
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4909660407381527, 内存占用:0.070381165, 工夫:2023-05-12 18:56:58
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5010152071141755, 内存占用:0.07123947, 工夫:2023-05-12 18:56:59
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.49105254146305255, 内存占用:0.071811676, 工夫:2023-05-12 18:57:00
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.5006662712686883, 内存占用:0.07190704, 工夫:2023-05-12 18:57:01
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4910441572402714, 内存占用:0.07238388, 工夫:2023-05-12 18:57:02
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.48179546937329976, 内存占用:0.07247925, 工夫:2023-05-12 18:57:03
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4910992356040398, 内存占用:0.072574615, 工夫:2023-05-12 18:57:04
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4821774792079267, 内存占用:0.072574615, 工夫:2023-05-12 18:57:05
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4911388293884917, 内存占用:0.07276535, 工夫:2023-05-12 18:57:07
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.48256160243541957, 内存占用:0.07276535, 工夫:2023-05-12 18:57:08
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.47425299944273586, 内存占用:0.07276535, 工夫:2023-05-12 18:57:09
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.49955661852322975, 内存占用:0.07276535, 工夫:2023-05-12 18:57:10
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.49127618766141185, 内存占用:0.07276535, 工夫:2023-05-12 18:57:11
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.48322963628030124, 内存占用:0.07276535, 工夫:2023-05-12 18:57:12
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.49127264447960023, 内存占用:0.07286072, 工夫:2023-05-12 18:57:13
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4835066221066964, 内存占用:0.07286072, 工夫:2023-05-12 18:57:14
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.491327825886193, 内存占用:0.07286072, 工夫:2023-05-12 18:57:15
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4837757999712849, 内存占用:0.07286072, 工夫:2023-05-12 18:57:16
--------------- 分割线 ------------------
该过程的 cpu 占用率:0.4764742407081455, 内存占用:0.07286072, 工夫:2023-05-12 18:57:17
--------------- 分割线 ------------------
^Csignal: interrupt

失常运行状态下 cpu 占用率 0.5%,内存 0.07%

开启 100w 协程,并在 10s 后退出:

(改为 10s 打印一次 cpu/ 内存使用率,因为要继续要执行很久,察看其能降到的最低水位,1s 打印一次输入信息太多)


    go func() {pid := os.Getpid()
        fmt.Println("以后程序的过程号为:", pid)

        p, _ := process.NewProcess(int32(pid))
        for {v, _ := p.CPUPercent()
            //if v != 0.0 {
            if v > 0 {memPercent, _ := p.MemoryPercent()
                fmt.Printf("该过程的 cpu 占用率:%v, 内存占用:%v, 工夫:%v\n", v, memPercent, time.Now().Format(time.DateTime))
                println("--------------- 分割线 ------------------")
            }
            time.Sleep(10 * time.Second) // 改为 10s 打印一次
        }
    }()
最后!程序中 goroutine 的数量为:3,allg 大小为 8
以后程序的过程号为:42424
该过程的 cpu 占用率:11.971746677840297, 内存占用:0.25568008, 工夫:2023-05-12 19:03:20
--------------- 分割线 ------------------
最初!程序中 goroutine 的数量为:1000003,allg 大小为 1000016
该过程的 cpu 占用率:34.861478672353634, 内存占用:9.252453, 工夫:2023-05-12 19:03:30
--------------- 分割线 ------------------
5s 后程序中 goroutine 的数量为:112316,allg 大小为 1000016
10s 后程序中 goroutine 的数量为:3,allg 大小为 1000016
该过程的 cpu 占用率:29.03536881788808, 内存占用:7.668495, 工夫:2023-05-12 19:03:40
--------------- 分割线 ------------------
15s 后程序中 goroutine 的数量为:3,allg 大小为 1000016
20s 后程序中 goroutine 的数量为:3,allg 大小为 1000016
该过程的 cpu 占用率:19.596219249163674, 内存占用:4.980278, 工夫:2023-05-12 19:03:50
--------------- 分割线 ------------------
25s 后程序中 goroutine 的数量为:3,allg 大小为 1000016
30s 后程序中 goroutine 的数量为:3,allg 大小为 1000016
该过程的 cpu 占用率:14.765560187265695, 内存占用:3.2074928, 工夫:2023-05-12 19:04:00
--------------- 分割线 ------------------
该过程的 cpu 占用率:11.864651607929765, 内存占用:3.2078743, 工夫:2023-05-12 19:04:10
--------------- 分割线 ------------------
该过程的 cpu 占用率:9.92047143662269, 内存占用:2.5784492, 工夫:2023-05-12 19:04:20
--------------- 分割线 ------------------
该过程的 cpu 占用率:8.514160477329295, 内存占用:2.0552635, 工夫:2023-05-12 19:04:30
--------------- 分割线 ------------------
该过程的 cpu 占用率:7.470054280536214, 内存占用:0.313282, 工夫:2023-05-12 19:04:40
--------------- 分割线 ------------------
该过程的 cpu 占用率:6.6560274549231, 内存占用:0.31404495, 工夫:2023-05-12 19:04:50
--------------- 分割线 ------------------
该过程的 cpu 占用率:5.994422368627717, 内存占用:0.31461716, 工夫:2023-05-12 19:05:00
--------------- 分割线 ------------------
该过程的 cpu 占用率:5.461318917906727, 内存占用:0.315094, 工夫:2023-05-12 19:05:10
--------------- 分割线 ------------------
该过程的 cpu 占用率:5.008342574208281, 内存占用:0.31614304, 工夫:2023-05-12 19:05:20
--------------- 分割线 ------------------
该过程的 cpu 占用率:4.654969744301823, 内存占用:0.8195877, 工夫:2023-05-12 19:05:30
--------------- 分割线 ------------------
该过程的 cpu 占用率:4.820140366604827, 内存占用:14.359665, 工夫:2023-05-12 19:05:40
--------------- 分割线 ------------------
该过程的 cpu 占用率:4.500030058742756, 内存占用:13.126755, 工夫:2023-05-12 19:05:50
--------------- 分割线 ------------------
该过程的 cpu 占用率:4.2259707867464344, 内存占用:12.516117, 工夫:2023-05-12 19:06:00
--------------- 分割线 ------------------
该过程的 cpu 占用率:3.9781804524533233, 内存占用:12.020016, 工夫:2023-05-12 19:06:10
--------------- 分割线 ------------------
该过程的 cpu 占用率:3.763355102804472, 内存占用:11.795521, 工夫:2023-05-12 19:06:20
--------------- 分割线 ------------------
该过程的 cpu 占用率:3.5711050662623522, 内存占用:11.4871025, 工夫:2023-05-12 19:06:30
--------------- 分割线 ------------------
该过程的 cpu 占用率:3.3930419833323433, 内存占用:10.962296, 工夫:2023-05-12 19:06:41
--------------- 分割线 ------------------
该过程的 cpu 占用率:3.24134631024968, 内存占用:10.740185, 工夫:2023-05-12 19:06:51
--------------- 分割线 ------------------
该过程的 cpu 占用率:3.094450487143189, 内存占用:10.542583, 工夫:2023-05-12 19:07:01
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.9646177374065994, 内存占用:4.2229652, 工夫:2023-05-12 19:07:11
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.8455099006903772, 内存占用:4.224205, 工夫:2023-05-12 19:07:21
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.731950644261066, 内存占用:4.186821, 工夫:2023-05-12 19:07:31
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.6384527809604403, 内存占用:1.7879486, 工夫:2023-05-12 19:07:41
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.687898384609942, 内存占用:5.225086, 工夫:2023-05-12 19:07:51
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.716562379472545, 内存占用:5.219364, 工夫:2023-05-12 19:08:01
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.6264727031567454, 内存占用:5.22089, 工夫:2023-05-12 19:08:11
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.5424541634929, 内存占用:5.221081, 工夫:2023-05-12 19:08:21
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.4638048279049034, 内存占用:5.2215576, 工夫:2023-05-12 19:08:31
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.3869673474972863, 内存占用:5.2212715, 工夫:2023-05-12 19:08:41
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.3207941879635903, 内存占用:5.220318, 工夫:2023-05-12 19:08:51
--------------- 分割线 ------------------
该过程的 cpu 占用率:2.255579978246484, 内存占用:5.2199364, 工夫:2023-05-12 19:09:01
--------------- 分割线 ------------------
^Csignal: interrupt

即使运行很久(协程都曾经完结了),cpu 和内存占用率,还是降不到 case1 这种水位

勾销正文再启 5w 个协程的这段代码,

package main

import (
    "fmt"
    "net/http"
    _ "net/http/pprof"
    "runtime"
    "time"
)

func main() {go func() {fmt.Println(http.ListenAndServe(fmt.Sprintf("127.0.0.1:%s", "6060"), nil))
    }()

    //go func() {
    //
    //    pid := os.Getpid()
    //    fmt.Println("以后程序的过程号为:", pid)
    //
    //    p, _ := process.NewProcess(int32(pid))
    //    for {//        v, _ := p.CPUPercent()
    //        //if v != 0.0 {
    //        if v > 0 {
    //
    //            memPercent, _ := p.MemoryPercent()
    //            fmt.Printf("该过程的 cpu 占用率:%v, 内存占用:%v, 工夫:%v\n", v, memPercent, time.Now().Format(time.DateTime))
    //            println("--------------- 分割线 ------------------")
    //        }
    //        time.Sleep(10 * time.Second)
    //    }
    //}()

    fmt.Printf("最后!程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    for i := 0; i < 1000000; i++ {go func() {time.Sleep(time.Second * 10)
        }()}

    time.Sleep(5e9)
    fmt.Printf("最初!程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)

    fmt.Printf("5s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("10s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("15s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("20s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    for i := 0; i < 50000; i++ {go func() {time.Sleep(time.Second * 10)
        }()}

    time.Sleep(5e9)

    fmt.Printf("25s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    time.Sleep(5e9)
    fmt.Printf("30s 后程序中 goroutine 的数量为:%d,allg 大小为 %d\n", runtime.NumGoroutine(), runtime.PrintAllgSize())

    // 用于阻塞不使程序退出
    select {}

    // 或者用这种形式
    //    //ch := make(chan int, 0)
    //    //ch <- 1
}

输入为:

最后!程序中 goroutine 的数量为:2,allg 大小为 6
最初!程序中 goroutine 的数量为:1000002,allg 大小为 1000014
5s 后程序中 goroutine 的数量为:2304,allg 大小为 1000014
10s 后程序中 goroutine 的数量为:2,allg 大小为 1000014
15s 后程序中 goroutine 的数量为:2,allg 大小为 1000014
20s 后程序中 goroutine 的数量为:2,allg 大小为 1000014
25s 后程序中 goroutine 的数量为:50002,allg 大小为 1000014
30s 后程序中 goroutine 的数量为:2,allg 大小为 1000014

即 allg 的元素大小没有持续增大

能够了解为 撑大了,缩减不回去了

或者设想有这样一个模型:

我是一个向导,带游客住酒店。我最开始带 10 集体,酒店经在我名下安顿 10 个房间;第二次带了 12 集体,原来我名下有 10 个房间,又要再开两个房间(开房间 / 退房间老本很高);第三次带了 15 集体,而我名下最多能住 12 人,所以还要持续开房间,挂在我名下的房间数量达到了 15 个; 第四次我带了 8 集体,就不须要进行低廉的开房 / 退房操作了。。。偏向于这种只扩张,不回缩的策略。另外,保洁阿姨须要每天打扫我名下的房间,等同于程序的 GC

但有一天黄金周,带了 10000 人来 … 那要持续开 9900 多个房,依照这个策略,游客走了之后不会退。但平时理论只有 10 几位游客,而空余的,allgs 元素都是指针类型,gc 须要遍历打扫,CPU 压力大

总是按最高的来接待

开 100w 个不退出的协程(能够把协程里的 time.Sleep 改为 100000s)

输入:

最后!程序中 goroutine 的数量为:3,allg 大小为 8
以后程序的过程号为:44057
该过程的 cpu 占用率:5.475696577050808, 内存占用:0.25539398, 工夫:2023-05-12 19:27:29
--------------- 分割线 ------------------
最初!程序中 goroutine 的数量为:1000003,allg 大小为 1000016
该过程的 cpu 占用率:38.08525996243931, 内存占用:15.448475, 工夫:2023-05-12 19:27:39
--------------- 分割线 ------------------
5s 后程序中 goroutine 的数量为:1000003,allg 大小为 1000019
10s 后程序中 goroutine 的数量为:1000003,allg 大小为 1000019
该过程的 cpu 占用率:20.09341974543954, 内存占用:10.235214, 工夫:2023-05-12 19:27:49
--------------- 分割线 ------------------
15s 后程序中 goroutine 的数量为:1000003,allg 大小为 1000020
20s 后程序中 goroutine 的数量为:1000003,allg 大小为 1000020
该过程的 cpu 占用率:13.63253311326255, 内存占用:8.337402, 工夫:2023-05-12 19:27:59
--------------- 分割线 ------------------
25s 后程序中 goroutine 的数量为:1000003,allg 大小为 1000020
30s 后程序中 goroutine 的数量为:1000003,allg 大小为 1000020
该过程的 cpu 占用率:10.340321837419848, 内存占用:6.0424805, 工夫:2023-05-12 19:28:09
--------------- 分割线 ------------------
该过程的 cpu 占用率:8.335432645556027, 内存占用:4.1745186, 工夫:2023-05-12 19:28:19
--------------- 分割线 ------------------
该过程的 cpu 占用率:6.971018163404126, 内存占用:3.056717, 工夫:2023-05-12 19:28:29
--------------- 分割线 ------------------
该过程的 cpu 占用率:6.004975767818229, 内存占用:0.9484291, 工夫:2023-05-12 19:28:39
--------------- 分割线 ------------------
该过程的 cpu 占用率:5.277065756593829, 内存占用:0.7973671, 工夫:2023-05-12 19:28:49
--------------- 分割线 ------------------

该过程的 cpu 占用率:4.698122263473828, 内存占用:0.099658966, 工夫:2023-05-12 19:28:59
--------------- 分割线 ------------------
该过程的 cpu 占用率:4.2436491766935704, 内存占用:0.10023117, 工夫:2023-05-12 19:29:09
--------------- 分割线 ------------------
该过程的 cpu 占用率:3.8619599362252104, 内存占用:0.10147095, 工夫:2023-05-12 19:29:19
--------------- 分割线 ------------------
该过程的 cpu 占用率:3.559485285903543, 内存占用:0.101947784, 工夫:2023-05-12 19:29:29
--------------- 分割线 ------------------
^Csignal: interrupt

本文由 mdnice 多平台公布

退出移动版