这种情况下 goroutine 的泄露应该怎么处理

2020-03-09 20:46:20 +08:00
 matrix67
package main

import (
	"bufio"
	"fmt"
	"github.com/sparrc/go-ping"
	"log"
	"os"
	"time"
)

type PingRestult struct {
	ip   string
	time time.Duration
}

func MultiPing(urls []string, ch chan PingRestult) {
	for {
		for _, url := range urls {
			go PingIp(url, ch)
		}
		time.Sleep(1 * time.Second)
	}

}

func GetResult(ch chan PingRestult) {
	for {
		fmt.Println(<-ch)
	}
}

func PingIp(ip string, ch chan PingRestult) {
	pinger, err := ping.NewPinger(ip)
	pinger.SetPrivileged(true)
	if err != nil {
		panic(err)
	}
	pinger.Count = 1
	pinger.Timeout = time.Second * 1

	pinger.Run()
	// blocks until finished
	stats := pinger.Statistics()
	result := PingRestult{ip, stats.AvgRtt}
	ch <- result
}

func main() {
	file, err := os.Open("file.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	var lines []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

	ch := make(chan PingRestult, len(lines))
	defer close(ch)

	go GetResult(ch)
	MultiPing(lines, ch)

	select {}

}

在 file.txt 有 ip 列表,取出来,放在一个死循环中得到 ping 的结果。

但是 MultiPing 中好像会有内存泄露,应该是回收的地方做的不对。

问问大佬们这种咋处理。感谢。

2329 次点击
所在节点    Go 编程语言
15 条回复
reus
2020-03-09 21:16:01 +08:00
这不叫泄露,你代码本来就不限制并发量。
reus
2020-03-09 21:16:57 +08:00
guonaihong
2020-03-09 21:19:33 +08:00
刚刚看了你用的库的文档,例子里有 Stop 函数,ping 结束你调用下 Stop 函数看下呢。
Mohanson
2020-03-09 21:23:08 +08:00
生产者消费者模型找个教程看下吧,你没有限制消费者数量。
zhuyuefeng
2020-03-10 00:42:51 +08:00
请问具体 goroutine 泄露的表现是什么呢?在 pprof 中发现 无论 ping 任务是否结束 goroutine 都保持在一个很高的数吗?

还是在执行 ping 的任务时很多,执行完数量就回落了呢?

同样,如果在 pprof 的 goroutine 中会打印函数调用栈信息,可以发现究竟都在哪发生了阻塞,可以快速定位出造成阻塞的 goroutine 代码。
GreyYang
2020-03-10 09:20:58 +08:00
看了下 go-ping 这个库,猜测可能是并发量大导致了死锁,有 issue: https://github.com/sparrc/go-ping/issues/77,可以尝试按照 issue 描述的方式增加 channel 容量,看看能否解决.
matrix67
2020-03-10 09:59:02 +08:00
@zhuyuefeng #5 我主要是不确定,MultiPing 方法里面 for 死循环中,里面开 goroutine,这些 goroutine 会被回收吗,还是会泄露。我试试逻辑里面不去调用 ping,就直接把传入 PingIp 的值放入 ch 中,看看这种情况下是否会泄露。
@GreyYang #6 试过增加容量,还是有问题。应该不是。问题应该是 reus 所说的,要用一个 pool 池。
zunceng
2020-03-10 14:13:29 +08:00
就问你 发请求的函数没有 ctx 参数 心里虚不虚
hzzhzzdogee
2020-03-10 14:21:01 +08:00
没细看, 猜测要在 PingIP 函数加超时 context 吧, 同意楼上
zhuyuefeng
2020-03-10 14:22:53 +08:00
@matrix67 MultiPing 按照我的理解,是主 Goroutine 执行的,本身如果是死循环的话,主程序 不会终止。
此外就是楼上说到的,如果 ping 那边有阻塞的话,也会使得调用其的 goroutine 阻塞了。建议可以看看 ping 那边的超时参数
matrix67
2020-03-10 15:03:21 +08:00
@zunceng
@hzzhzzdogee #9
@zhuyuefeng #10 超时那边应该是 pinger.Timeout = time.Second * 1 这个吧,我设置了。

报告各位大佬,首先我的第一个实现版本是有问题。 现在我按照 http://jmoiron.net/blog/limiting-concurrency-in-go/ 这个里面的用法,以及用了 workerpool 的用法,我发现是不是这个 go-ping 库有问题啊,我把 PingIp 这个函数变为直接打印出来,就啥错误都没了。https://pastebin.com/89KknAMV
matrix67
2020-03-10 16:06:52 +08:00
好吧 查出来了,我自己写的有问题。
hzzhzzdogee
2020-03-10 16:22:06 +08:00
@matrix67 具体是什么问题呢? 我看了下 go-ping 这个库, 似乎直接设置下 pinger.Timeout 就行
kuro1
2020-03-11 16:32:51 +08:00
@matrix67 问题解决了要反馈下楼上的热心老哥
matrix67
2020-03-11 19:59:35 +08:00
@hzzhzzdogee
@kuro1

我这个版本,写的不太好。MultiPing 这边 for 循环里面写了个 go PingIp,相当于无限消费者。所以内存爆炸了。

新写了一个版本,生产者读取 file.txt 送到一个 chan 里面去,然后 main 函数里面开 goroutine 作为消费者,消费 chan 里面的 ip 就行了。goroutine 的个数可以自己控制,也可以按照二楼老哥的方案,弄个池子。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/651325

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX