• 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)
    
  • 相关阅读:
    函数二
    python控制台输出带颜色的文字方法
    is 和 == 的区别
    基本数据类型(dict)
    基本数据类型(list,tuple)
    基本数据类型(int,bool,str)
    Python运算符与编码
    Java并发编程:synchronized
    泛型中? super T和? extends T的区别
    java中的匿名内部类总结
  • 原文地址:https://www.cnblogs.com/Otiger/p/16221859.html
Copyright © 2020-2023  润新知