如何使用通道知道循环启动的所有goroutine何时完成

时间:2015-12-05 04:51:31

标签: go goroutine

我目前正试图通过range map来执行并发数据库请求,而不是同步,显然是因为速度提升。

我的问题是我有这样的事情:

var mainthreads = make(chan *mainthread)
var mainthreadsFetched = make(chan struct{})
for containerid := range containers {
    go func() {
        rows, err := db.Query("SELECT thread_id, belongs_to, thread_name, access_level FROM forum_mainthread WHERE belongs_to = ?", containerid)
        defer rows.Close()
        if err != nil {
            log.Println(err)
        }
        for rows.Next() {
            mainthread := &MainThread{}
            err := rows.Scan(&mainthread.MainThreadID, &mainthread.BelongsTo, &mainthread.ThreadName, &mainthread.AccessLevel)
            if err != nil {
                log.Println(err)
            }
            mainthreads <- mainthread
        }
    }()
    mainthreadsFetched <- struct{}{}
}

// Get all mainthreads
<-mainthreadsFetched
// Do other stuff after complete

显然mainthreadsFetched <- struct{}{}几乎立即被调用,因为循环完成的速度比你能闪烁的速度快,我怎样才能为每个循环创建一个新的通道,不会阻止每个新的goroutine启动,而是让循环开始全部goroutines,然后在每个goroutine完成后发送到频道。

3 个答案:

答案 0 :(得分:3)

使用sync.WaitGroup是一个很好的解决方案,也是通常使用的解决方案。

或者,你可以在mainthreadsFetched len(containers)次收到,而不是只收到1.这将保证所有的例程都已完成。您需要将发送行移动到go例程的末尾(或者,更好的是,延迟)。

此外,由于containerid位于for循环中,因此其值会发生变化。您需要将它作为参数传递给go例程闭包。

答案 1 :(得分:0)

因此,我能做到这一点的最佳方法是使用sync.WaitGroup并执行以下操作:

var wg sync.WaitGroup
var mainThreadFetched = make(chan MainThread)
for containerid := range containers {
    wg.Add(1)
    go func(containerid int64) {
        rows, err := db.Query("SELECT thread_id, belongs_to, thread_name, access_level FROM forum_mainthread WHERE belongs_to = ?", containerid)
        defer rows.Close()
        if err != nil {
            log.Println(err)
        }
        for rows.Next() {
            mainthread := MainThread{}
            err := rows.Scan(&mainthread.MainThreadID, &mainthread.BelongsTo, &mainthread.ThreadName, &mainthread.AccessLevel)
            if err != nil {
                log.Println(err)
            }
            mainThreadFetched <- mainthread
        }
        wg.Done()
    }(containerid)
}

go func() {
    wg.Wait()
    close(mainThreadFetched)
}()

for mainthread := range mainThreadFetched {
    containers[mainthread.BelongsTo].MainThreads = append(containers[mainthread.BelongsTo].MainThreads, mainthread)
}

// Do other stuff

现在我可以从mainThreadFetched频道阅读,然后当WaitGroup满意时它将关闭频道,允许循环结束并继续

答案 2 :(得分:0)

我不知道你在哪里阅读主线程。如果它不是缓冲通道,您需要以某种方式解决。我将提供一些解决方案 - 没有更多&#34;正确&#34;比另一个 - 它只取决于你的需求。

变体A 这是最简单的解决方案,但它假设你有一些其他的goroutine阅读主线程(可能已经是这种情况)

var mainthreads = make(chan *mainthread)
var mainthreadsFetched = make(chan struct{})
go somethingWhichReadsMainThreads()
for containerid := range containers {
    go func(containerid int) {
        // build query omitted for brevity
        for rows.Next() {
            // omitted for brevity
            mainthreads <- mainthread
        }
        mainthreadsFetched <- struct{}{}
    }(containerid)
}

for i := 0; i < len(containers); i++ {
    <-mainThreadsFetched
}
close(mainthreads)
// Do other stuff after complete

变体B 这个使用select语句来处理与完成通知分开读取线程而不需要另外的goroutine。

var mainthreads = make(chan *mainthread)
var mainthreadsFetched = make(chan struct{})
for containerid := range containers {
    go func(containerid int) {
        // build query omitted for brevity
        for rows.Next() {
            // omitted for brevity
            mainthreads <- mainthread
        }
        mainthreadsFetched <- struct{}{}
    }(containerid)
}

numComplete := 0
readRunning := true
for readRunning {
    select {
    case thread := <-mainthreads:
        // do something with thread, like threads = append(threads, thread)
    case <-mainthreadsFetched:
        numFetched++
        if numFetched == len(containers) {
            readRunning = False
        }
    }
}
// Do other stuff after complete

变体C 这个使用的事实是你没有使用零值&#39; (nil)用于传递实际数据,因此您可以将其用作信号值而不是单独的结构通道。它的优点是代码少得多,但它确实感觉像是远处的怪异动作。

var mainthreads = make(chan *mainthread)
for containerid := range containers {
    go func(containerid int) {
        // build query omitted for brevity
        for rows.Next() {
            // omitted Scan for brevity
            mainthreads <- mainthread
        }
        mainthreads <- nil // nil signals to us we are done
    }(containerid)
}

numComplete := 0
for thread := range mainthreads {
    if thread != nil {
        // do something with thread, like threads = append(threads, thread)
    } else {
        numFetched++
        if numFetched == len(containers) {
            break
        }
    }
}
// Do other stuff after complete