2016-10-14 3 views
2

Да, это похоже на один из наиболее дублированных вопросов в StackOverflow, но, пожалуйста, уделите несколько минут на вопрос.Канал закрыт, но все goroutines спали - deadlock

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

Что не так с моими гортанами?

+1

взаимоблокировка происходит потому, что вызов 'close' никогда не достигается. 'ok' в приеме канала будет false только тогда, когда канал был закрыт, и поэтому выполнение застряло в этом цикле. Вы должны рассмотреть возможность использования цикла диапазона (http://dave.cheney.net/2014/03/19/channel-axioms). Если вы в порядке с переработкой дизайна немного, что-то вроде этого может работать как подсказка: https://play.golang.org/p/aiuiyueyzB – abhink

+1

да, закрыть канал в недоступном, но это не корень проблемы здесь и нет, выполнение застрянет только в случае бесконечных URL-адресов. –

ответ

2

Ну после первого взгляда, вы можете сделать короче для просто используя диапазон нравится:

for urls := range UrlChannel { ... }

будет перебирать, пока канал не закрыт, и он выглядит намного лучше.

Также у Вас есть ранний возвращение в первом если вашей функции _Crawl(), так что если первое условие истинно, функция закончится, и ничего не передается в канал, так что код, что получает от этого канала будет ждать вечно.

Другая вещь, внутри вашей второй для твоих goroutines для каждого URL-адреса, но вы их не ждете, и на самом деле эти goroutines попытаются отправить что-то на закрытый канал. Похоже, что этого не происходит, потому что в этом случае код будет паниковать, для этого вы можете использовать WaitGroup.

В резюме у вас есть код с несколькими возможными условиями блокировки.

||| Super Edit |||:

Я должен написать вам, что ваш код является своего рода грязным, и решение может быть простым WaitGroup, но я боялся, что вы чувствуете себя плохо, потому что я обнаружил слишком много проблем, но если вы действительно хотите научиться как писать параллельный код, вы должны сначала подумать в коде или псевдокоде без параллелизма, а затем попытаться добавить магию.

В вашем случае то, что я вижу рекурсивное решение, поскольку URL извлекаются из 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) 
} 

теперь второй этап идентификации параллельного кода, Легко в этом случае, так как каждый URL может быть выбран одновременно (иногда параллельно), все, что мы должны добавить, это WaitGroup и создать goroutine для каждого URL-адреса, теперь просто нужно обновить только для для извлечения URL-адресов (это только для блок):

// 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 
// ... 

Будущие соображения, может быть, вы хотите, чтобы контролировать количество goroutines (или рабочих) для этого вы должны использовать что-то вроде вентилятора в или веером, вы можете найти больше здесь: https://blog.golang.org/advanced-go-concurrency-patterns и https://blog.golang.org/pipelines

Но не бойтесь создавать тысячи goroutines в 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, спасибо! Ваш ответ совершенно ясен. Я питонист, поэтому для меня это новое чувство, когда параллелизм так прост;) Я ценю вашу работу вокруг ответа. – I159

+0

awesome, мне нравится Python много, это мой второй любимый язык (после Go). Это трюк, пишущий код, который работает сначала, а затем вы увидите параллелизм более легко, также помните, что одновременный не означает параллелизм https://blog.golang.org/concurrency-is-not-parallelism –

Смежные вопросы