如何在不使用时间的情况下等待所有goroutine完成。睡眠?

时间:2013-08-13 11:22:40

标签: go synchronization goroutine

此代码选择同一文件夹中的所有xml文件,与调用的可执行文件一样,并且异步地将处理应用于回调方法中的每个结果(在下面的示例中,只打印出文件的名称)。

如何避免使用sleep方法来保持main方法退出?我在绕通道缠绕时遇到问题(我认为这是需要的,同步结果)所以任何帮助都会受到赞赏!

package main

import (
    "fmt"
    "io/ioutil"
    "path"
    "path/filepath"
    "os"
    "runtime"
    "time"
)

func eachFile(extension string, callback func(file string)) {
    exeDir := filepath.Dir(os.Args[0])
    files, _ := ioutil.ReadDir(exeDir)
    for _, f := range files {
            fileName := f.Name()
            if extension == path.Ext(fileName) {
                go callback(fileName)
            }
    }
}


func main() {
    maxProcs := runtime.NumCPU()
    runtime.GOMAXPROCS(maxProcs)

    eachFile(".xml", func(fileName string) {
                // Custom logic goes in here
                fmt.Println(fileName)
            })

    // This is what i want to get rid of
    time.Sleep(100 * time.Millisecond)
}

5 个答案:

答案 0 :(得分:133)

您可以使用sync.WaitGroup。引用链接的示例:

package main

import (
        "net/http"
        "sync"
)

func main() {
        var wg sync.WaitGroup
        var urls = []string{
                "http://www.golang.org/",
                "http://www.google.com/",
                "http://www.somestupidname.com/",
        }
        for _, url := range urls {
                // Increment the WaitGroup counter.
                wg.Add(1)
                // Launch a goroutine to fetch the URL.
                go func(url string) {
                        // Decrement the counter when the goroutine completes.
                        defer wg.Done()
                        // Fetch the URL.
                        http.Get(url)
                }(url)
        }
        // Wait for all HTTP fetches to complete.
        wg.Wait()
}

答案 1 :(得分:48)

WaitGroups绝对是执行此操作的规范方式。但是,为了完整起见,这里是WaitGroups引入之前常用的解决方案。基本的想法是使用一个频道来说“我已经完成”,并让主要的goroutine等待,直到每个衍生例程报告完成。

func main() {
    c := make(chan struct{}) // We don't need any data to be passed, so use an empty struct
    for i := 0; i < 100; i++ {
        go func() {
            doSomething()
            c <- struct{}{} // signal that the routine has completed
        }()
    }

    // Since we spawned 100 routines, receive 100 messages.
    for i := 0; i < 100; i++ {
        <- c
    }
}

答案 2 :(得分:3)

sync.WaitGroup可以在这里帮到你。

package main

import (
    "fmt"
    "sync"
    "time"
)


func wait(seconds int, wg * sync.WaitGroup) {
    defer wg.Done()

    time.Sleep(time.Duration(seconds) * time.Second)
    fmt.Println("Slept ", seconds, " seconds ..")
}


func main() {
    var wg sync.WaitGroup

    for i := 0; i <= 5; i++ {
        wg.Add(1)   
        go wait(i, &wg)
    }
    wg.Wait()
}

答案 3 :(得分:0)

尽管sync.waitGroup(wg)是一种典型的前进方式,但确实需要您在进行wg.Add之前至少进行一些wg.Wait通话,才能完成所有通话。对于诸如Web爬网程序之类的简单事物而言,这可能是不可行的,在这种情况下,您事先不知道递归调用的数量,并且检索驱动wg.Add调用的数据需要花费一些时间。毕竟,您需要先加载并解析第一页,然后才能知道第一批子页的大小。

我使用渠道编写了一个解决方案,在解决方案中避免了Tour of Go - web crawler练习中的waitGroup。每次启动一个或多个go例程,您就将数字发送到children频道。每当go例程即将完成时,您就向1通道发送done。当孩子的总数等于完成的总数时,我们就完成了。

我唯一剩下的问题是results通道的硬编码大小,但这是(当前)Go的限制。


// recursionController is a data structure with three channels to control our Crawl recursion.
// Tried to use sync.waitGroup in a previous version, but I was unhappy with the mandatory sleep.
// The idea is to have three channels, counting the outstanding calls (children), completed calls 
// (done) and results (results).  Once outstanding calls == completed calls we are done (if you are
// sufficiently careful to signal any new children before closing your current one, as you may be the last one).
//
type recursionController struct {
    results  chan string
    children chan int
    done     chan int
}

// instead of instantiating one instance, as we did above, use a more idiomatic Go solution
func NewRecursionController() recursionController {
    // we buffer results to 1000, so we cannot crawl more pages than that.  
    return recursionController{make(chan string, 1000), make(chan int), make(chan int)}
}

// recursionController.Add: convenience function to add children to controller (similar to waitGroup)
func (rc recursionController) Add(children int) {
    rc.children <- children
}

// recursionController.Done: convenience function to remove a child from controller (similar to waitGroup)
func (rc recursionController) Done() {
    rc.done <- 1
}

// recursionController.Wait will wait until all children are done
func (rc recursionController) Wait() {
    fmt.Println("Controller waiting...")
    var children, done int
    for {
        select {
        case childrenDelta := <-rc.children:
            children += childrenDelta
            // fmt.Printf("children found %v total %v\n", childrenDelta, children)
        case <-rc.done:
            done += 1
            // fmt.Println("done found", done)
        default:
            if done > 0 && children == done {
                fmt.Printf("Controller exiting, done = %v, children =  %v\n", done, children)
                close(rc.results)
                return
            }
        }
    }
}

Full source code for the solution

答案 4 :(得分:0)

这是使用WaitGroup的解决方案。

首先,定义2个实用程序方法:

package util

import (
    "sync"
)

var allNodesWaitGroup sync.WaitGroup

func GoNode(f func()) {
    allNodesWaitGroup.Add(1)
    go func() {
        defer allNodesWaitGroup.Done()
        f()
    }()
}

func WaitForAllNodes() {
    allNodesWaitGroup.Wait()
}

然后,替换对callback的调用:

go callback(fileName)

调用您的实用程序功能

util.GoNode(func() { callback(fileName) })

最后一步,将此行添加到main的末尾,而不是sleep的末尾。这样可以确保主线程在程序可以停止之前正在等待所有例程完成。

func main() {
  // ...
  util.WaitForAllNodes()
}