协程泄漏原因
- 协程阻塞,未能如期结束
- 协程阻塞最常见的原因都跟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)