V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
jss
V2EX  ›  Go 编程语言

[请教] 运行一段时间后报: goroutine 10 [chan send, 20 minutes]

  •  1
     
  •   jss · 2020-06-02 14:03:02 +08:00 · 3487 次点击
    这是一个创建于 1642 天前的主题,其中的信息可能已经有所发展或是发生改变。

    由于业务需要,服务器通过后台可自定义时间段轮询通过 websocket 向前端发送数据,但是运行一段时间后报错:

    | goroutine 2 [chan send, 20 minutes]: |
    | serve_api/internal/app/v1/service.workers.func1(0xc0001b4480, 0xc000026980, 0x12) Users/jssmac/Documents/project/golang/serve_api/internal/app/v1/service/isv.go:60 +0x63|
    

    关键代码

    //读取可查询数据列表
    func GetIsvList() (interface{}, error) {
    	var (
    		newList []interface{}
    		list    []models.LsvInfo
    	)
    	models.Find(&list, map[string]interface{}{"status": 1})
    	if len(list) > 0 {
    		//等待组完成
    		var wg sync.WaitGroup
    		//声明等待数
    		wg.Add(len(list))
    		for _, item := range list {
    			go func(name string) {
    				var c1 = worker(name)
    				n := <-c1
    				newList = append(newList, n)
    				wg.Done()
    			}(item.Item)
    		}
    		wg.Wait()
    		return newList, nil
    	} else {
    		return models.Array, nil
    	}
    }
    // chan
    func worker(name string) chan interface{} {
    	out := make(chan interface{})
    	go func(i string) {
    		for {
    			data := getIsvInfo(i)
    			out <- data  //原文件 isv.go:60 指向本行
    		}
    	}(name)
    	return out
    }
    //获取 ISV 数据
    func getIsvInfo(name string) *Result {
    	urls := "http://192.168.1.188:8000/isv/"
    	params := url.Values{
    		"name": []string{name},
    	}
    	res, _ := request("GET", urls, []byte(params.Encode()))
    	regeo := new(Result)
    	json.Unmarshal(res, &regeo)
    	return regeo
    }
    //request 请求包装
    func request(method, url string, data []byte) (body []byte, err error) {
    	if method == "GET" {
    		url = fmt.Sprint(url, "?", string(data))
    		data = nil
    	}
    	client := http.Client{Timeout: 10 * time.Second}
    	req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
    	if err != nil {
    		return body, err
    	}
    	resp, err := client.Do(req)
    	if err != nil {
    		return nil, err
    	}
    	body, err = ioutil.ReadAll(resp.Body)
    	defer resp.Body.Close()
    	if err != nil {
    		return body, err
    	}
    	return body, err
    }
    
    14 条回复    2020-07-08 13:50:59 +08:00
    asAnotherJack
        1
    asAnotherJack  
       2020-06-02 14:34:24 +08:00
    worker 里为什么又开了一个 goroutine,而且这个 goroutine 里死循环的意义是什么,这边循环不断地写 out channel,但是外边只取了一次,第二次 send 就一直阻塞了
    tiedan
        2
    tiedan  
       2020-06-02 14:43:09 +08:00
    worker 为什么是死循环啊?
    jss
        3
    jss  
    OP
       2020-06-02 15:01:56 +08:00
    @asAnotherJack 那我不用 go func ,直接 for { data := getAspenPoint(name) out <- data } 吗?
    jss
        4
    jss  
    OP
       2020-06-02 15:03:25 +08:00
    @tiedan 那我这个 worker 要怎么写,能详细描述一下吗?
    asAnotherJack
        5
    asAnotherJack  
       2020-06-02 15:30:51 +08:00
    @jss #3 外边调用 worker 的时候已经在一个子协程里了,里边没必要再开一个了,也不需要用一个 channel 返回结果,直接 return getIsvInfo(name)。
    BlackBerry999
        6
    BlackBerry999  
       2020-06-02 15:41:51 +08:00
    把 out := make(chan interface{})放到 worker(name string)里面??
    jss
        7
    jss  
    OP
       2020-06-02 16:13:10 +08:00
    @asAnotherJack 好的,我试试看
    useben
        8
    useben  
       2020-06-02 16:36:40 +08:00   ❤️ 1
    ```go
    func main() {
    var (
    sum = 100000
    wg sync.WaitGroup
    )
    rs := make(chan bool, sum)
    for i := 0; i < sum; i++ {
    wg.Add(1)
    go func(i int) {
    defer wg.Done()
    ping(rs, i, "http://www.baidu.com")
    }(i)
    }

    wg.Wait()
    close(rs)

    sl := make([]interface{}, 0, sum)
    for v := range rs {
    sl = append(sl, v)
    }

    fmt.Println(sl)
    }

    func ping(rs chan bool, i int, urlStr string) {
    //reqquest url
    fmt.Println(i, urlStr)

    rs <- true
    }
    ```

    模拟你的需求写了个
    asAnotherJack
        9
    asAnotherJack  
       2020-06-02 16:50:26 +08:00   ❤️ 1
    你的代码还有个问题,newList = append(newList, n) 这句代码在多个协程里 append newList 这个共享变量了,会有并发问题,可以把所有的结果先放入一个 channel,在 channel 的消费端统一 append 。
    jss
        10
    jss  
    OP
       2020-06-02 16:55:42 +08:00
    @useben 好的,我研究一下
    jss
        11
    jss  
    OP
       2020-06-02 16:58:24 +08:00
    @asAnotherJack 好的,你看看 8 楼代码,我按照他 @useben 这种思路写?
    asAnotherJack
        12
    asAnotherJack  
       2020-06-02 17:28:09 +08:00
    @jss #11 可以的
    sonxzjw
        13
    sonxzjw  
       2020-06-03 08:25:46 +08:00
    只看错误提示,大概意思是你这个送消息到 chan 的操作,阻塞了 20 分钟了,怀疑运行时死循环你自个儿检查下。
    jss
        14
    jss  
    OP
       2020-07-08 13:50:59 +08:00
    @useben 老哥,这段代码在运行一个月后 又又又 出问题了,能帮我看看嘛
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2494 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 02:28 · PVG 10:28 · LAX 18:28 · JFK 21:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.