GO协程泄漏

协程泄漏原因

  • 协程阻塞,未能如期结束
  • 协程阻塞最常见的原因都跟channel有关
  • 由于每个协程都要占用内存,所以协程泄漏也会导致内存泄漏
  • 示例代码
    • fooChan := make(chan struct{}) // 协程泄漏地方
    • 因为没有使用缓冲区,则一直堵塞在fooChan <- struct{}{}
package main

import (
	"context"
	"fmt"
	"runtime"
	"time"
)

func foo() {
	time.Sleep(100 * time.Millisecond)
	return
}

func handleRequest(timeout time.Duration) bool {
	ctx, cancle := context.WithCancel(context.Background())
	// make(chan struct{}, 1)则不泄漏
	fooChan := make(chan struct{}) // 协程泄漏地方 
	go func() {
		foo()
		fooChan <- struct{}{}
	}()

	go func() {
		time.Sleep(timeout)
		cancle() // 调用,关闭ctx
	}()

	select {
	case <-fooChan:
		fmt.Println("foo finish")
	case <-ctx.Done(): // 接口返回超时
		// fmt.Println("timeout")
	}
	return true
}

func main() {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()
	go func() {
		for {
			<-ticker.C
			fmt.Printf("go routine number %d\n", runtime.NumGoroutine())
		}
	}()
	for {
		handleRequest(50 * time.Millisecond)

	}
}

协程泄漏排查

  • 重点代码
import (
	"net/http"
	_ "net/http/pprof"
)

func main() {
	go func() {
		if err := http.ListenAndServe("localhost:8080", nil); err != nil {
			panic(err)
		}
	}()
}
  • 访问地址:http://127.0.0.1:8080/debug/pprof/goroutine?debug=1

    • total 协程数量
    • 查看哪部分使用的协程数量比较多
    goroutine profile: total 507 -------------------------------------一共507协程
    498 @ 0x43af15 0x40556b 0x405325 0x73fcb9 0x4692d1 ----------------------------------这里使用了498个协程
    #	0x73fcb8	main.handleRequest.func1+0x48	E:/GO_project/timeout.go:23
    
  • 示例代码

    • fooChan := make(chan struct{}) // 协程泄漏地方
    • 因为没有使用缓冲区,则一直堵塞在fooChan <- struct{}{}
package main

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

func foo() {
	time.Sleep(100 * time.Millisecond)
	return
}

func handleRequest(timeout time.Duration) bool {
	ctx, cancle := context.WithCancel(context.Background())
	// make(chan struct{}, 1)则不泄漏
	fooChan := make(chan struct{}) // 协程泄漏地方 
	go func() {
		foo()
		fooChan <- struct{}{}
	}()

	go func() {
		time.Sleep(timeout)
		cancle() // 调用,关闭ctx
	}()

	select {
	case <-fooChan:
		fmt.Println("foo finish")
	case <-ctx.Done(): // 接口返回超时
		// fmt.Println("timeout")
	}
	return true
}

func main() {
	go func() {
		if err := http.ListenAndServe("localhost:8080", nil); err != nil {
			panic(err)
		}
	}()
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()
	go func() {
		for {
			<-ticker.C
			fmt.Printf("go routine number %d\n", runtime.NumGoroutine())
		}
	}()
	for {
		handleRequest(50 * time.Millisecond)

	}
}

命令行终端命令

  • go tool pprof http://0.0.0.0:8080/debug/pprof/goroutine
  • Saved profile in 保存路径
    • go tool pprof --http=:8081 C:\Users\pprof\pprof.goroutine.001.pb.gz
    • 访问http://localhost:8081,可视化SVG
  • Type 当前类型
  • Time 当前时间
  • top 查看当前情况
    • 越往下,越能体现底层原因
  • list 函数名 // 查看代码每一行开辟的goroutine数量
    • 5225 23: fooChan <- struct{}{} // 开辟了5225个goroutine
  • traces 打印调用堆栈
  • web 生成SVG文件,可视化图
$ go tool pprof http://0.0.0.0:8080/debug/pprof/goroutine
Fetching profile over HTTP from http://0.0.0.0:8080/debug/pprof/goroutine
Saved profile in C:\Users\pprof\pprof.goroutine.001.pb.gz
Type: goroutine
Time: Oct 7, 2021 at 12:23am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)top
Showing nodes accounting for 5233, 100% of 5234 total
Dropped 44 nodes (cum <= 26)
      flat  flat%   sum%        cum   cum%
      5233   100%   100%       5233   100%  runtime.gopark
         0     0%   100%       5227 99.87%  main.handleRequest.func1
         0     0%   100%       5225 99.83%  runtime.chansend
         0     0%   100%       5225 99.83%  runtime.chansend1
(pprof)list main.handleRequest.func1
Total: 5234
ROUTINE ======================== main.handleRequest.func1 in E:\GO_project\k8sdev\timeout.go
         0       5227 (flat, cum) 99.87% of Total
         .          .     17:func handleRequest(timeout time.Duration) bool {
         .          .     18:   ctx, cancle := context.WithCancel(context.Background())
         .          .     19:   // make(chan struct{}, 1)则不泄漏
         .          .     20:   fooChan := make(chan struct{}) // 协程泄漏地方
         .          .     21:   go func() {
         .          2     22:           foo()
         .       5225     23:           fooChan <- struct{}{}
         .          .     24:   }()
         .          .     25:
         .          .     26:   go func() {
         .          .     27:           time.Sleep(timeout)
         .          .     28:           cancle() // 调用,关闭ctx
(pprof)traces
Type: goroutine
Time: Oct 7, 2021 at 12:23am (CST)
-----------+-------------------------------------------------------
      5225   runtime.gopark
             runtime.chansend
             runtime.chansend1
             main.handleRequest.func1
-----------+-------------------------------------------------------
         2   runtime.gopark
             time.Sleep
             main.foo
             main.handleRequest.func1
-----------+-------------------------------------------------------
         1   runtime.gopark
             runtime.chanrecv
             runtime.chanrecv1
             main.main.func2
-----------+-------------------------------------------------------
         1   runtime.gopark
             runtime.netpollblock
             internal/poll.runtime_pollWait
             internal/poll.(*pollDesc).wait
             internal/poll.(*ioSrv).ExecIO
             internal/poll.(*FD).Read
             net.(*netFD).Read
             net.(*conn).Read
             net/http.(*connReader).backgroundRead
-----------+-------------------------------------------------------
         1   runtime.gopark
             runtime.netpollblock
             internal/poll.runtime_pollWait
             internal/poll.(*pollDesc).wait
             internal/poll.(*ioSrv).ExecIO
             internal/poll.(*FD).Read
             net.(*netFD).Read
             net.(*conn).Read
             net/http.(*connReader).Read
             bufio.(*Reader).fill
             bufio.(*Reader).ReadSlice
             bufio.(*Reader).ReadLine
             net/textproto.(*Reader).readLineSlice
             net/textproto.(*Reader).ReadLine
             net/http.readRequest
             net/http.(*conn).readRequest
             net/http.(*conn).serve
-----------+-------------------------------------------------------
         1   runtime.gopark
             runtime.netpollblock
             internal/poll.runtime_pollWait
             internal/poll.(*pollDesc).wait
             internal/poll.(*ioSrv).ExecIO
             internal/poll.(*FD).acceptOne
             internal/poll.(*FD).Accept
             net.(*netFD).accept
             net.(*TCPListener).accept
             net.(*TCPListener).Accept
             net/http.(*Server).Serve
             net/http.(*Server).ListenAndServe
             net/http.ListenAndServe
             main.main.func1
-----------+-------------------------------------------------------
         1   runtime.gopark
             runtime.selectgo
             main.handleRequest
             main.main
             runtime.main
-----------+-------------------------------------------------------
         1   runtime.gopark
             time.Sleep
             main.handleRequest.func2
-----------+-------------------------------------------------------
         1   runtime/pprof.writeRuntimeProfile
             runtime/pprof.writeGoroutine
             runtime/pprof.(*Profile).WriteTo
             net/http/pprof.handler.ServeHTTP
             net/http/pprof.Index
             net/http.HandlerFunc.ServeHTTP
             net/http.(*ServeMux).ServeHTTP
             net/http.serverHandler.ServeHTTP
             net/http.(*conn).serve
-----------+-------------------------------------------------------
(pprof)
posted @ 2022-05-04 18:15  rxg456  阅读(270)  评论(1)    收藏  举报