首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用sync.WaitGroup panics完成的WebCrawl练习。我做错了什么?Go惯用解决方案是什么?

使用sync.WaitGroup panics完成的WebCrawl练习。我做错了什么?Go惯用解决方案是什么?
EN

Stack Overflow用户
提问于 2021-11-17 16:34:31
回答 1查看 27关注 0票数 0

我正在用他们的“围棋之旅”学习围棋。我设法做了所有的练习,但最后一个练习让我很沮丧。它正随着fatal error: all goroutines are asleep - deadlock!而消亡

代码语言:javascript
复制
package main

import (
    "fmt"
    "sync"
)

type Fetcher interface {
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

var urlCache = make(map[string]bool)
var mutex = sync.Mutex{}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher, wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Printf("Crawl: %v \n", url)

    if depth <= 0 {
        fmt.Println("Crawl: reached depth")
        return
    }

    mutex.Lock()
    if alreadyFetched := urlCache[url]; alreadyFetched {
        fmt.Printf("Crawl: %v already fetched\n", url)
        return
    }
    urlCache[url] = true
    mutex.Unlock()

    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Crawl: found %s %q\n", url, body)
    for _, u := range urls {
        wg.Add(1)
        go Crawl(u, depth-1, fetcher, wg)
    }
    return
}

func main() {
    var wg sync.WaitGroup

    fmt.Println("Main: Starting worker")
    wg.Add(1)
    go Crawl("https://golang.org/", 4, fetcher, &wg)
    fmt.Println("Main: Waiting for workers to finish")

    fmt.Println("Main: Completed")

    wg.Wait()
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
    body string
    urls []string
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
    if res, ok := f[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
    "https://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "https://golang.org/pkg/",
            "https://golang.org/cmd/",
        },
    },
    "https://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "https://golang.org/",
            "https://golang.org/cmd/",
            "https://golang.org/pkg/fmt/",
            "https://golang.org/pkg/os/",
        },
    },
    "https://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "https://golang.org/",
            "https://golang.org/pkg/",
        },
    },
    "https://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "https://golang.org/",
            "https://golang.org/pkg/",
        },
    },
}

任何提示和帮助都将非常感谢。谢谢你。

Edit1:内联代码

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-11-17 16:49:46

这里:

代码语言:javascript
复制
    mutex.Lock()
    if alreadyFetched := urlCache[url]; alreadyFetched {
        fmt.Printf("Crawl: %v already fetched\n", url)
        return
    }
    urlCache[url] = true
    mutex.Unlock()

if条件为真时,在不解锁共享互斥锁的情况下返回。

因此,最终其他goroutine将在mutex.Lock()上死机,因为获取它的goroutine从未发布。

在返回之前,还要在if块中调用mutex.Unlock()

您还可以在锁定之后和if语句之前使用defer mutex.Unlock(),在普通的应用程序中,这不会产生明显的差异,但在实际场景中,您希望将资源保持在尽可能短的时间内。如果您有一个包含其他长时间运行的操作的函数体,则可以在if之后立即解锁。但是,如果if可以将控制流返回给调用者,则必须记住释放锁。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70008225

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档