【问题标题】:There are many threads reserved while golang application runninggolang 应用程序运行时保留了许多线程
【发布时间】:2017-11-24 03:54:55
【问题描述】:

golang应用程序是一个通过调用c库接收文件,保存到磁盘并报告传输状态以http协议监控服务的工具。

经过几次传输,我发现大约有 70 多个线程存在几个 goroutine。

我检查了 c 和 go 源代码,没有发现线程或 goroutine 泄漏。 我使用“dlv”来调试应用程序,这是其中一个线程的堆栈:

(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

不知道这些线程是从哪里来的或者可能是golang运行时的线程池?

谁能看一下,非常感谢!

【问题讨论】:

  • 请显示代码...您可以使用play.golang.org
  • 对不起,这是公司的项目。我不能在这里发布代码。我看了上面提到的堆栈的源代码,似乎睡眠逻辑还可以,调度程序将“m”置于睡眠状态并等待下一次唤醒。但是每次调度器都会启动一个新的“m”来运行任务,所以之前的“m”不会被唤醒,会一点一点地消耗线程。

标签: multithreading go goroutine


【解决方案1】:

问题

golang 应用程序是一个通过调用 c 来接收文件的工具 库,将其保存到磁盘并报告传输状态以进行监控 使用 http 协议的服务。

经过几次传输,我发现大约有 70+ 个线程存在 有几个 goroutine。

原因

对 C 的每次调用(通过 cgo 或 Windows 等上的 syscall)都不是真的 与执行 OS 系统调用不同,只要 Go 调度程序 很担心。

会发生什么:

  1. 当一个 goroutine 被执行时,它运行在一个 OS 线程上 (这很明显,我理解)。

  2. 当它执行系统调用或调用 C 时,该 goroutine 阻塞 (停止执行 Go 代码)。

  3. Go 运行时调度程序监视被阻塞的 goroutine 然后在东一个单一的“调度程序滴答声”(目前 - 在 Go 1.8 和 1.9 — 是 20 µs) 通过,goroutine 仍然被阻塞, 还有其他可运行的 goroutine, 调度器创建另一个操作系统线程来创建其他 goroutines 继续执行。

这种行为一开始可能看起来违反直觉 但是如果没有它,比如说,一台双 CPU 的机器,你只需要调用 两个系统调用(例如读取或写入文件)并行从 任意两个 goroutine 阻塞其余的活动 goroutine 从做他们的工作。 换句话说,调度器试图跟上 Go 的承诺 总是有多达GOMAXPROCS goroutines 运行 如果有 goroutines 想要运行,GOMAXPROCS 设置为机器的 CPU(核心)数。

因此,如果您有相当高的 C 调用流失率,而这些调用的完成速度比单个调度程序的周期慢,那么您将拥有不断增长的池 分配的操作系统线程数。

请注意,这本身并不坏:当然,您将分配资源 (在典型的商品操作系统上,每个线程分配了大约 8 MiB 的堆栈 加上操作系统内部的一些簿记数据结构),但它们是 不浪费:这些线程将在需要时立即被重用。 比如说,您的下一次此类 C 调用将重用分配的线程。

解决办法

不过,如果您想防止这种情况发生,常用方法 是合理地序列化你的 C 调用。

一个典型的方法是有一个单一的“工人”goroutine 它接收“任务”——通常以某种类型的值的形式 您创建的自定义类型 - 通过通道并发送结果 他们通过另一个渠道执行。

输入通道可以被缓冲——有效地将它变成一个队列。

如果您仍想并行化这项工作,您可以拥有一个 worker goroutines — 全部读取单个输入通道并写入 单输出通道。

但请注意,如果这些 C 调用将大部分时间用于磁盘 I/O 他们读/写的文件位于文件系统上 由单一媒体支持,您通常不会获得太多收益 并行化,除非该介质非常快——例如 SSD 或 内存 (RAM) 磁盘。

因此,请考虑所有选项并仔细考虑您的设计。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-04
    • 1970-01-01
    • 2016-10-25
    • 1970-01-01
    相关资源
    最近更新 更多