2016-10-14 36 views
2

是的,它看起来像StackOverflow上最重复的问题之一,但请花几分钟时间回答这个问题。一个通道关闭了,但所有的goroutines都睡着了 - 死锁

func _Crawl(url string, fetcher Fetcher, ch chan []string) { 
    if store.Read(url) == true { 
     return 
    } else { 
     store.Write(url) 
    } 

    body, urls, err := fetcher.Fetch(url) 
    if err != nil { 
     fmt.Printf("not found: %s\n", url) 
    } 
    fmt.Printf("found: %s %q\n", url, body) 
    ch <- urls 
} 

func Crawl(url string, fetcher Fetcher) { 
    UrlChannel := make(chan []string, 4) 
    go _Crawl(url, fetcher, UrlChannel) 
    for urls, ok := <- UrlChannel; ok; urls, ok = <- UrlChannel{ 
     for _, i := range urls { 
      go _Crawl(i, fetcher, UrlChannel) 
     } 
    } 
    close(UrlChannel) //The channel is closed. 
} 

func main() { 
    Crawl("http://golang.org/", fetcher) 
} 

我在循环结束后关闭通道。该程序返回正确的结果,但在最后引发错误:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:

main.Crawl(0x113a2f, 0x12, 0x1800d0, 0x10432220)

/tmp/sandbox854979773/main.go:55 +0x220

main.main()

/tmp/sandbox854979773/main.go:61 +0x60

我的goroutines有什么问题?

+1

死锁发生是因为调用'close'永远不会到达。只有在通道关闭时,通道接收中的“ok”才会为false,因此执行会停留在该回路中。你应该考虑使用范围循环(http://dave.cheney.net/2014/03/19/channel-axioms)。如果你对重新设计这个设计有点不错,像这样的东西可以作为一个提示:https://play.golang.org/p/aiuiyueyzB – abhink

+1

是的,关闭频道不可达,但这不是问题的根源这里并没有,执行将卡住只有无限的网址 –

回答

2

先看看好了后,你可以做较短只是用系列,如:

for urls := range UrlChannel { ... }

它会遍历直到通道被关闭,它看起来好多了。

你也需要在第一早期回报如果你的函数_Crawl()的,因此,如果这第一个条件是实现的功能将结束,什么都不会传递到渠道,所以代码时接收从那个频道将永远等待。

其他的东西,里面你的第二个 your're创建每个URL够程,但你不能等着他们,实际上这些够程将尝试发送的东西封闭通道。看起来这不会发生,因为在这种情况下代码会惊慌失措,您可以使用WaitGroup

在简历中,您已经有了一些可能出现死锁情况的代码。

|||超级编辑|||

我应该写信给你,你的代码有点凌乱,解决方案可能是一个简单的WaitGroup,但我害怕让你感觉不好,因为我发现了太多问题,但如果你真的想学习如何编写并发代码,您应该首先考虑代码或伪代码,而无需并发,然后尝试添加魔法。

在你的情况我所看到的是一个递归的解决方案,因为该网址是从树的形式的HTML文档获取,将是这样一个DFS

func crawl(url string, fetcher Fetcher) { 
    // if we've visited this url just stop the recursion 
    if store.Read(url) == true { 
     return 
    } 
    store.Write(url) 

    body, urls, err := fetcher.Fetch(url) 
    if err != nil { 
     fmt.Printf("not found: %s\n", url) 
     return // early return if there's no urls there's no reason to continue 
    } 
    fmt.Printf("found: %s %q\n", url, body) 

    // this part will change !! 
    // ... 
    for _, url := range urls { 
     crawl(url, fetcher) 
    } 
    // 
} 

func main() { 
    crawl("http://golang.org", fetcher) 
} 

现在第二步确定并发代码,容易在这种情况下,因为每个网址可以同时获取(有时并行),我们必须添加一个WaitGroup并为每个网址创建一个goroutine,现在只需更新来获取网址(它只是for block):

// this code will be in the comment: "this part will change !!" 
// 
// this var is just a thread-safe counter 
var wg sync.WaitGroup 

// set the WaitGroup counter with the len of urls slice 
wg.Add(len(urls)) 

for _, url := range urls { 

    // it's very important pass the url as a parameter 
    // because the var url changes for each loop (url := range) 
    go func(u string) { 

     // Decrement the counter (-1) when the goroutine completes 
     defer wg.Done() 

     crawl(u, fetcher) 

    }(url) 
} 

wg.Wait() // wait for all your goroutines 
// ... 

未来的考虑,也许你想控制够程(或工人)的,你必须使用类似扇入或扇出数,你可以找到更多在这里: https://blog.golang.org/advanced-go-concurrency-patternshttps://blog.golang.org/pipelines

但是,不要害怕在Go创造数千个够程的他们very cheap

注:我没有编译的代码,也许有一个小错误的地方 :)

1

上述两种解决方案和range通过信道循环都具有相同的问题。 问题是,一个通道关闭后循环会结束,但循环结束后通道将被关闭。所以我们需要知道什么时候关闭打开的频道。我相信我们需要计算开始的工作(goroutines)。但在这种情况下,我只是失去了一个计数器变量。由于这是一项巡回演习,因此不应该太复杂。

func _Crawl(url string, fetcher Fetcher, ch chan []string) { 
    if store.Read(url) == false { 
     store.Write(url)  
     body, urls, err := fetcher.Fetch(url) 
     if err != nil { 
      fmt.Printf("not found: %s\n", url) 
     } else { 
      fmt.Printf("found: %s %q\n", url, body) 
     } 
     ch <- urls 
    } 
} 

func Crawl(url string, depth int, fetcher Fetcher) { 
    UrlChannel := make(chan []string, 4) 
    go _Crawl(url, fetcher, UrlChannel) 
    for urls := range UrlChannel { 
     for _, url := range urls { 
      go _Crawl(url, fetcher, UrlChannel) 
     } 
     depth-- 
     if depth < 0 { 
      close(UrlChannel) 
     } 
    } 
} 
+0

我编辑了我的答案,只是试图按照我的代码中的意见,只是让我知道它是否适合你 –

+1

@YandryPozo,谢谢!你的回答非常清楚。我是一名pythonist,所以当并发性如此简单时,对我来说这是一种新感觉;)我感谢您围绕答案所做的工作。 – I159

+0

真棒,我很喜欢Python,这是我第二喜欢的语言(在Go之后)。这是最先工作的技巧编写代码,然后您将更容易看到并发性,并且记住并发并不意味着并行性https://blog.golang.org/concurrency-is-not-parallelism –

相关问题