Golang Go语言程序跑了一段时候后留下了好多操作系统线程,没多少 goroutine
程序是调个 c 库实现文件收发,并将传输中的状态通过 http 发送给另外的监控服务。 发现测试过一段时候以后,会有很多的线程,但是用 dlv 调试看没多少 goroutines,而且日志也显示为传输开的 goutine 都退出了。昨天测试一下,竟然开了 1000 多个线程。
以下是其中一个 thread 的 stack:
(dlv) bt
0 0x000000000046df03 in runtime.futex
at /home/vagrant/resource/go/src/runtime/sys_linux_amd64.s:388
1 0x0000000000437e92 in runtime.futexsleep
at /home/vagrant/resource/go/src/runtime/os_linux.go:45
2 0x000000000041e042 in runtime.notesleep
at /home/vagrant/resource/go/src/runtime/lock_futex.go:145
3 0x000000000044036d in runtime.stopm
at /home/vagrant/resource/go/src/runtime/proc.go:1594
4 0x0000000000441178 in runtime.findrunnable
at /home/vagrant/resource/go/src/runtime/proc.go:2021
5 0x0000000000441cec in runtime.schedule
at /home/vagrant/resource/go/src/runtime/proc.go:2120
6 0x0000000000442063 in runtime.park_m
at /home/vagrant/resource/go/src/runtime/proc.go:2183
7 0x0000000000469f1b in runtime.mcall
at /home/vagrant/resource/go/src/runtime/asm_amd64.s:240
通过网络了解了一些 go 的调度器的知识,也看了上述相关的代码,没发现有什么异常,就是 thread(即所谓的 m)本身退出过程是正确的,应该会 futex_wait,等待下一次调度。 但貌似之后有 goroutine 都不会再使用这个 m 来调度,需要新开线程来执行,所以造成线程越来越多。
go 版本是 1.7.1, 有没有大侠能够指点一下迷津,谢谢。
Golang Go语言程序跑了一段时候后留下了好多操作系统线程,没多少 goroutine
更多关于Golang Go语言程序跑了一段时候后留下了好多操作系统线程,没多少 goroutine的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你不是用 c 的库,直接使用 Go 开发呢?
更多关于Golang Go语言程序跑了一段时候后留下了好多操作系统线程,没多少 goroutine的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
go 会等到一定程度的时候会回收的吧
c 库实现了网络加速部分,go 建立上层的产品逻辑。
没回收,上周五出现的,周一来看还是 1000 多个线程,周末没有测试。
检查你 go 调 c 的代码
是不是调一次就多一个不会回收的线程
也不是,几天也测了好久,但是线程数没有增加,还是 1018 个。感觉像某些情况下被触发,不是每次都是。另外 c 代码里也 check 了下,没发现线程泄露。
很明显是调用 C 库重复,也许对 cgo 的理解不够?
C 库里会启动线程?
建议了解 runtime.GOMAXPROCS()
cgo 如果没有记错的话和长时间 syscall 一样会启动新线程执行。
cgo 调用并发多的时候会开更多的线程,但是现在 go 的实现线程空闲后不会退出,看这个 issue:
https://github.com/golang/go/issues/14592
https://www.cockroachlabs.com/blog/the-cost-and-complexity-of-cgo/
简单来说,对于 cgo 的并发调用会导致创建新的线程
cgo 调用的执行流直接在系统线程上,而不是 goroutine 上,不受 go 调度器管理
感谢!
应该就是这个问题。c 库通过回调通知事件,回调里不清楚如何调 go,我用 pthread_cond_t 条件变量封装了一下,回调里 signal 这个 cond,在 go 里 wait 这个 cond。应该就是这个操作阻塞了线程,之后如果有并发任务会重新开线程,造成好多线程悬挂。
在Go语言中,goroutine与操作系统线程之间存在一种抽象层,Go运行时(runtime)会管理这些goroutine并优化它们在操作系统线程上的调度。如果你发现程序运行一段时间后操作系统线程数量显著增加,而goroutine数量并不多,这可能是由于以下几种情况:
-
线程泄漏:虽然Go的goroutine管理机制相对健壮,但如果你使用了cgo或系统调用(特别是阻塞调用),可能会导致操作系统线程无法被正确回收。检查所有外部系统调用和cgo的使用,确保它们能正确释放资源。
-
运行时配置:Go的运行时配置可能会影响线程的创建和回收。例如,
GOMAXPROCS
环境变量设置了Go程序可以同时使用的CPU核心数,这间接影响线程池的大小。检查并适当调整这些配置。 -
监控工具:使用Go的内置pprof工具或外部监控工具(如htop, top等)来精确观察goroutine和线程的状态。这可以帮助你识别是否存在意外的线程增长。
-
代码审查:对代码进行审查,特别是那些涉及并发和异步操作的部分。确保所有goroutine在完成任务后都能正确退出,没有意外的阻塞或死锁。
如果上述步骤仍未解决问题,可能需要更深入地分析Go的运行时行为或考虑是否有必要向Go社区或相关专家求助。