2015-01-11 3 views
2

У меня есть код ниже, который извлекает список URL-адресов, а затем условно загружает файл и сохраняет его в файловой системе. Файлы выбираются одновременно, и главный goroutine ждет всех файлов, которые будут извлечены. Но программа никогда не выходит (и ошибок нет) после завершения всех запросов.golang sync.WaitGroup никогда не завершает

То, что я думаю, что происходит, что-то количество их процедур в WaitGroup либо увеличивается слишком много, чтобы начать с (через Add) или не уменьшается на достаточно (а Done вызова не происходит).

Есть ли что-то, что я, очевидно, делаю неправильно? Как бы я проверил, сколько сейчас выполняются подпрограммы в WaitGroup, поэтому я могу лучше отладить, что происходит?

package main 

import (
    "fmt" 
    "io" 
    "io/ioutil" 
    "net/http" 
    "os" 
    "strings" 
    "sync" 
) 

func main() { 
    links := parseLinks() 

    var wg sync.WaitGroup 

    for _, url := range links { 
     if isExcelDocument(url) { 
      wg.Add(1) 
      go downloadFromURL(url, wg) 
     } else { 
      fmt.Printf("Skipping: %v \n", url) 
     } 
    } 
    wg.Wait() 
} 

func downloadFromURL(url string, wg sync.WaitGroup) error { 
    tokens := strings.Split(url, "/") 
    fileName := tokens[len(tokens)-1] 
    fmt.Printf("Downloading %v to %v \n", url, fileName) 

    content, err := os.Create("temp_docs/" + fileName) 
    if err != nil { 
     fmt.Printf("Error while creating %v because of %v", fileName, err) 
     return err 
    } 

    resp, err := http.Get(url) 
    if err != nil { 
     fmt.Printf("Could not fetch %v because %v", url, err) 
     return err 
    } 
    defer resp.Body.Close() 

    _, err = io.Copy(content, resp.Body) 
    if err != nil { 
     fmt.Printf("Error while saving %v from %v", fileName, url) 
     return err 
    } 

    fmt.Printf("Download complete for %v \n", fileName) 

    defer wg.Done() 
    return nil 
} 

func isExcelDocument(url string) bool { 
    return strings.HasSuffix(url, ".xlsx") || strings.HasSuffix(url, ".xls") 
} 

func parseLinks() []string { 
    linksData, err := ioutil.ReadFile("links.txt") 
    if err != nil { 
     fmt.Printf("Trouble reading file: %v", err) 
    } 

    links := strings.Split(string(linksData), ", ") 

    return links 
} 
+1

Вы откладываете 'wg.Done()', но у вас все еще есть его в конце функции. Попробуйте переместить его в начало, прежде чем у него появится шанс вернуться от ошибки. –

+5

Передайте указатель на 'wg' функции, а не на структуру. – plusmid

ответ

20

Есть две проблемы с этим кодом. Во-первых, вы должны передать указатель на WaitGroup на downloadFromURL(), иначе объект будет скопирован, а Done() не будет виден в main().

См:

func main() { 
    ... 
    go downloadFromURL(url, &wg) 
    ... 
} 

Во-вторых, defer wg.Done() должен быть один из первых операторов в downloadFromURL(), в противном случае, если вы вернетесь из функции до этого заявления, он не будет «зарегистрирован», и не получит называется.

func downloadFromURL(url string, wg *sync.WaitGroup) error { 
    defer wg.Done() 
    ... 
} 
+0

Это сделало трюк! Спасибо @Bartosz – srt32

+0

Стрела указателя. Решил мою проблему и узнал что-то новое. Благодаря! :) – C4u

3

Аргументы в Go всегда передаются по значению. Используйте указатель, если аргумент может быть изменен. Кроме того, убедитесь, что вы всегда выполнять wg.Done() .FOR пример,

package main 

import (
    "fmt" 
    "io" 
    "io/ioutil" 
    "net/http" 
    "os" 
    "strings" 
    "sync" 
) 

func main() { 
    links := parseLinks() 

    wg := new(sync.WaitGroup) 

    for _, url := range links { 
     if isExcelDocument(url) { 
      wg.Add(1) 
      go downloadFromURL(url, wg) 
     } else { 
      fmt.Printf("Skipping: %v \n", url) 
     } 
    } 
    wg.Wait() 
} 

func downloadFromURL(url string, wg *sync.WaitGroup) error { 
    defer wg.Done() 
    tokens := strings.Split(url, "/") 
    fileName := tokens[len(tokens)-1] 
    fmt.Printf("Downloading %v to %v \n", url, fileName) 

    content, err := os.Create("temp_docs/" + fileName) 
    if err != nil { 
     fmt.Printf("Error while creating %v because of %v", fileName, err) 
     return err 
    } 

    resp, err := http.Get(url) 
    if err != nil { 
     fmt.Printf("Could not fetch %v because %v", url, err) 
     return err 
    } 
    defer resp.Body.Close() 

    _, err = io.Copy(content, resp.Body) 
    if err != nil { 
     fmt.Printf("Error while saving %v from %v", fileName, url) 
     return err 
    } 

    fmt.Printf("Download complete for %v \n", fileName) 

    return nil 
} 

func isExcelDocument(url string) bool { 
    return strings.HasSuffix(url, ".xlsx") || strings.HasSuffix(url, ".xls") 
} 

func parseLinks() []string { 
    linksData, err := ioutil.ReadFile("links.txt") 
    if err != nil { 
     fmt.Printf("Trouble reading file: %v", err) 
    } 

    links := strings.Split(string(linksData), ", ") 

    return links 
} 
0

Как уже упоминалось @Bartosz, вам необходимо передать ссылку на ваш WaitGroup объекта. Он отлично справился с обсуждением важности defer ws.Done()

Мне нравится WaitGroup. Однако мне не нравится, что нам нужно передать ссылку на goroutine, потому что это будет означать, что логика параллелизма будет смешана с вашей бизнес-логикой.

Так что я пришел с этой общей функции, чтобы решить эту проблему для меня:

// Parallelize parallelizes the function calls 
func Parallelize(functions ...func()) { 
    var waitGroup sync.WaitGroup 
    waitGroup.Add(len(functions)) 

    defer waitGroup.Wait() 

    for _, function := range functions { 
     go func(copy func()) { 
      defer waitGroup.Done() 
      copy() 
     }(function) 
    } 
} 

Так что ваш пример может быть решена следующим образом:

func main() { 
    links := parseLinks() 

    functions := []func(){} 
    for _, url := range links { 
     if isExcelDocument(url) { 
      function := func(url string){ 
       return func() { downloadFromURL(url) } 
      }(url) 

      functions = append(functions, function) 
     } else { 
      fmt.Printf("Skipping: %v \n", url) 
     } 
    } 

    Parallelize(functions...) 
} 

func downloadFromURL(url string) { 
    ... 
} 

Если вы хотели бы использовать его, вы можете найти его здесь https://github.com/shomali11/util

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

  • Нет связанных вопросов^_^