关于后端:瞬间高并发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多平台公布

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理