返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

9.2.4 泄漏

发布于 2024-10-12 19:15:49 字数 3659 浏览 0 评论 0 收藏 0

如果通道一直处于阻塞状态,那么会导致 goroutine 无法结束和回收,形成资源泄露。

package main

import (
	"time"
	"runtime"
)

func test() chan byte {
	c := make(chan byte)

	go func() {
		buf := make([]byte, 0, 10<<20) // 10MB
		for {
			d, ok := <- c
			if !ok { return }
			buf = append(buf, d)
		}
	}()

	return c
}

func main() {
	for i := 0; i < 5; i++ {
		test()
	}

	for {
		time.Sleep(time.Second)
		runtime.GC()
	}
}
# 因 goroutine 无法结束,其持有的 buf 内存无法回收。

$ GODEBUG=gctrace=1 ./test

gc 1 @0.002s 20%: ... 50->50->50 MB,  50 MB goal, ...
gc 2 @1.002s  0%: ... 50->50->50 MB, 100 MB goal, ... (forced)
gc 3 @2.009s  0%: ... 50->50->50 MB, 100 MB goal, ... (forced)
...
gc 7 @6.029s  0%: ... 50->50->50 MB, 100 MB goal, ... (forced)
# 可以观察到这些 goroutine 的状态。

$ GODEBUG="schedtrace=1000,scheddetail=1" ./test

SCHED 3008ms: gomaxprocs=2 idleprocs=2 threads=4 ...
  
  G1: status=4(sleep)
  G2: status=4(force gc (idle))
  G3: status=4(GC sweep wait)
  G4: status=4(GC scavenge wait)
  
  G5: status=4(chan receive) m=-1 lockedm=-1
  G6: status=4(chan receive) m=-1 lockedm=-1
  G7: status=4(chan receive) m=-1 lockedm=-1
  G8: status=4(chan receive) m=-1 lockedm=-1
  G9: status=4(chan receive) m=-1 lockedm=-1
  
  G10: status=4(GC worker (idle))
  G11: status=4(GC worker (idle))
// src/runtime/runtime2.go

const (
	// G status

	// _Gidle means this goroutine was just allocated and has not
	// yet been initialized.
	_Gidle = iota // 0

	// _Grunnable means this goroutine is on a run queue. It is
	// not currently executing user code. The stack is not owned.
	_Grunnable // 1

	// _Grunning means this goroutine may execute user code. The
	// stack is owned by this goroutine. It is not on a run queue.
	// It is assigned an M and a P (g.m and g.m.p are valid).
	_Grunning // 2

	// _Gsyscall means this goroutine is executing a system call.
	// It is not executing user code. The stack is owned by this
	// goroutine. It is not on a run queue. It is assigned an M.
	_Gsyscall // 3

	// _Gwaiting means this goroutine is blocked in the runtime.
	// It is not executing user code. It is not on a run queue,
	// but should be recorded somewhere (e.g., a channel wait
	// queue) so it can be ready()d when necessary. The stack is
	// not owned *except* that a channel operation may read or
	// write parts of the stack under the appropriate channel
	// lock. Otherwise, it is not safe to access the stack after a
	// goroutine enters _Gwaiting (e.g., it may get moved).
	_Gwaiting // 4

	// _Gmoribund_unused is currently unused, but hardcoded in gdb
	// scripts.
	_Gmoribund_unused // 5

	// _Gdead means this goroutine is currently unused. It may be
	// just exited, on a free list, or just being initialized. It
	// is not executing user code. It may or may not have a stack
	// allocated. The G and its stack (if any) are owned by the M
	// that is exiting the G or that obtained the G from the free
	// list.
	_Gdead // 6

	// _Genqueue_unused is currently unused.
	_Genqueue_unused // 7

	// _Gcopystack means this goroutine's stack is being moved. It
	// is not executing user code and is not on a run queue. The
	// stack is owned by the goroutine that put it in _Gcopystack.
	_Gcopystack // 8

	// _Gpreempted means this goroutine stopped itself for a
	// suspendG preemption. It is like _Gwaiting, but nothing is
	// yet responsible for ready()ing it. Some suspendG must CAS
	// the status to _Gwaiting to take responsibility for
	// ready()ing this G.
	_Gpreempted // 9
)

通过添加 time.After 之类手段,让通道有解除阻塞的机会。

另外,不当使用 time.Tick ,也会引发泄漏。(详情参考《中卷:标准库》)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文