死锁问题+使用通道时增加goroutine的数量

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了死锁问题+使用通道时增加goroutine的数量相关的知识,希望对你有一定的参考价值。

我正在使用Go开发一个简单的http状态检查程序。该程序起初运行良好。然后,我向其中引入了channel,但它并没有停止。换句话说,不会触发WorkGroup.Done()

如何触发WorkGroup.Done()

// go playground: https://play.golang.org/p/1AzXEAV9p4K
package main

import (
    "fmt"
    "net/http"
    "runtime"
    "sync"
)

type urlState struct {
    url   string
    state bool
}

func checkLink(link string, c chan<- urlState) {
    _, err := http.Get(link)
    if err != nil {
        fmt.Println(link, `is not working`)
        c <- urlState{
            url:   link,
            state: false,
        }
    } else {
        fmt.Println(link, `is working`)
        c <- urlState{
            url:   link,
            state: true,
        }
    }
}

func Check(links []string) bool {
    result := true
    stats := make(map[string]bool)
    var wg sync.WaitGroup
    fmt.Println(`total sites: `, len(links))
    wg.Add(len(links)) // add workgroups of exactly same amount as links array length

    c := make(chan urlState, len(links))
    for _, link := range links {
        stats[link] = false
        fmt.Println(`requesting...`, link)
        go checkLink(link, c)
        fmt.Println(`goroutines: `, runtime.NumGoroutine())
    }

    for v := range c {
        fmt.Println(`recv from channel, assigning result for `, v.url)
        stats[v.url] = v.state
        wg.Done()
        fmt.Println(`goroutines: `, runtime.NumGoroutine())
    }

    wg.Wait() // any codes below are not reached. increased number of goroutines seem to be the reason. 
    close(c)
    fmt.Println(`work finished!`)
    for key, value := range stats {
        fmt.Println(key, `---`, value)
        if value == false {
            result = false
        }
    }
    return result
}

func main() {
  links := []string{`https://goolgle.com`, `https://amazon.com`} // two goroutines are expected
  Check(links) // shows increased number of goroutines. and it goes deadlock
}
答案

程序在for v := range c上阻塞,因为通道上的范围一直持续到通道关闭为止。通过接收期望的值数进行修复。此更改不需要WaitGroup。

result := true
stats := make(map[string]bool)
fmt.Println(`total sites: `, len(links))

c := make(chan urlState, len(links))
for _, link := range links {
    stats[link] = false
    fmt.Println(`requesting...`, link)
    go checkLink(link, c)
    fmt.Println(`goroutines: `, runtime.NumGoroutine())
}

for i := 0; i < len(links); i++ {
    v := <-c
    fmt.Println(`recv from channel, assigning result for `, v.url)
    stats[v.url] = v.state
    fmt.Println(`goroutines: `, runtime.NumGoroutine())
}

fmt.Println(`work finished!`)
for key, value := range stats {
    fmt.Println(key, `---`, value)
    if value == false {
        result = false
    }
}

Run it on the playground

解决问题的另一种方法是在完成c例行程序后关闭checkLink。使用WaitGroup对此进行协调。

func checkLink(link string, c chan<- urlState, wg *sync.WaitGroup) {
    defer wg.Done()
    ... remainder of function is same as before
}

func Check(links []string) bool {
    result := true
    stats := make(map[string]bool)
    var wg sync.WaitGroup
    fmt.Println(`total sites: `, len(links))
    wg.Add(len(links)) // add workgroups of exactly same amount as links array length

    c := make(chan urlState, len(links))
    for _, link := range links {
        stats[link] = false
        fmt.Println(`requesting...`, link)
        go checkLink(link, c, &wg)
        fmt.Println(`goroutines: `, runtime.NumGoroutine())
    }

    // Close c when checkLink goroutines complete.
    go func() {
        wg.Wait()
        close(c)
    }()

    for v := range c {
        fmt.Println(`recv from channel, assigning result for `, v.url)
        stats[v.url] = v.state
        wg.Done()
        fmt.Println(`goroutines: `, runtime.NumGoroutine())
    }

    fmt.Println(`work finished!`)
    for key, value := range stats {
        fmt.Println(key, `---`, value)
        if value == false {
            result = false
        }
    }
    return result
}

Run it on the playground

以上是关于死锁问题+使用通道时增加goroutine的数量的主要内容,如果未能解决你的问题,请参考以下文章

为啥在同一个 goroutine 中使用无缓冲通道会导致死锁?

使用通道作为队列的死锁

与通道和 goroutine 同步

去通道和死锁

范围通道以死锁结束

Golang:为啥增加缓冲通道的大小会消除我的 goroutine 的输出?