始终有x个goroutines运行

时间:2014-08-14 10:40:48

标签: go goroutine

我看到很多关于如何让Go等待x个goroutine完成的教程和示例,但我想要做的是确保总是有x号运行,因此启动了一个新的goroutine一旦结束。

具体来说,我有几十万件待办事项'这是处理一些来自MySQL的东西。所以它的工作原理如下:

db, err := sql.Open("mysql", connection_string)
checkErr(err)
defer db.Close()

rows,err := db.Query(`SELECT id FROM table`)
checkErr(err)
defer rows.Close()

var id uint
for rows.Next() {
    err := rows.Scan(&id)
    checkErr(err)
    go processTheThing(id)
    }
checkErr(err)
rows.Close()

目前,它将启动数十万个processTheThing()的主题。我需要的是启动最多x个数字(我们称之为20个)goroutines。所以它首先为前20行启动20,然后从那时开始,它会在当前goroutine之一完成时为下一个id启动一个新的goroutine。所以在任何时候总有20个在运行。

我确信这很简单/标准,但我似乎无法找到任何教程或示例或如何完成此操作的良好解释。

8 个答案:

答案 0 :(得分:36)

您可能会发现Go Concurrency Patterns文章很有趣,尤其是 Bounded parallelism 部分,它会解释您需要的确切模式。

您可以使用channel of empty structs as a limiting guard to control number of concurrent worker goroutines

package main

import "fmt"

func main() {
    maxGoroutines := 10
    guard := make(chan struct{}, maxGoroutines)

    for i := 0; i < 30; i++ {
        guard <- struct{}{} // would block if guard channel is already filled
        go func(n int) {
            worker(n)
            <-guard
        }(i)
    }
}

func worker(i int) { fmt.Println("doing work on", i) }

答案 1 :(得分:13)

  1. 创建将数据传递给goroutines的渠道。
  2. 启动20个goroutine,循环处理来自通道的数据。
  3. 将数据发送到频道,而不是开始新的goroutine。

答案 2 :(得分:12)

在这里,我认为像这样简单的事情会起作用:

package main

import "fmt"

const MAX = 20

func main() {
    sem := make(chan int, MAX)
    for {
        sem <- 1 // will block if there is MAX ints in sem
        go func() {
            fmt.Println("hello again, world")
            <-sem // removes an int from sem, allowing another to proceed
        }()
    }
}

答案 3 :(得分:10)

GrzegorzŻur的answer是最有效的方法,但对于新手来说,如果不阅读代码就很难实现,所以这是一个非常简单的实现:

type idProcessor func(id uint)

func SpawnStuff(limit uint, proc idProcessor) chan<- uint {
    ch := make(chan uint)
    for i := uint(0); i < limit; i++ {
        go func() {
            for {
                id, ok := <-ch
                if !ok {
                    return
                }
                proc(id)
            }
        }()
    }
    return ch
}

func main() {
    runtime.GOMAXPROCS(4)
    var wg sync.WaitGroup //this is just for the demo, otherwise main will return
    fn := func(id uint) {
        fmt.Println(id)
        wg.Done()
    }
    wg.Add(1000)
    ch := SpawnStuff(10, fn)
    for i := uint(0); i < 1000; i++ {
        ch <- i
    }
    close(ch) //should do this to make all the goroutines exit gracefully
    wg.Wait()
}

playground

答案 4 :(得分:10)

感谢大家帮助我解决这个问题。但是,我并不觉得有人真的提供了既有效又简单/易懂的东西,尽管你们都帮我理解了这项技术。

我最终做的是作为对我的具体问题的回答,我认为更容易理解和实际,所以我会在这里发布,以防其他人有同样的问题。

不知怎的,这看起来很像OneOfOne发布的内容,这很好,因为现在我明白了。但OneOfOne的代码我发现起初很难理解,因为函数的函数传递让人很难理解什么是什么。我认为这种方式更有意义:

package main

import (
"fmt"
"sync"
)

const xthreads = 5 // Total number of threads to use, excluding the main() thread

func doSomething(a int) {
    fmt.Println("My job is",a)
    return
}

func main() {
    var ch = make(chan int, 50) // This number 50 can be anything as long as it's larger than xthreads
    var wg sync.WaitGroup

    // This starts xthreads number of goroutines that wait for something to do
    wg.Add(xthreads)
    for i:=0; i<xthreads; i++ {
        go func() {
            for {
                a, ok := <-ch
                if !ok { // if there is nothing to do and the channel has been closed then end the goroutine
                    wg.Done()
                    return
                }
                doSomething(a) // do the thing
            }
        }()
    }

    // Now the jobs can be added to the channel, which is used as a queue
    for i:=0; i<50; i++ {
        ch <- i // add i to the queue
    }

    close(ch) // This tells the goroutines there's nothing else to do
    wg.Wait() // Wait for the threads to finish
}

答案 5 :(得分:2)

这是一个简单的producer-consumer问题,在Go中可以使用通道缓冲paquets轻松解决。

简单来说:创建一个接受您ID的频道。运行一些例程,这些例程将在循环中从通道读取,然后处理ID。然后运行您的循环,将ID提供给频道。

示例:

func producer() {
    var buffer = make(chan uint)

    for i := 0; i < 20; i++ {
        go consumer(buffer)
    }

    for _, id :=  range IDs {
        buffer <- id
    }
}

func consumer(buffer chan uint) {
    for {
        id := <- buffer
        // Do your things here
    }
}

要了解的事情:

  • 无缓冲通道阻塞:如果不接受写入通道的项目,则该项目的例程将被阻止,直到
  • 我的例子没有关闭机制:你必须找到一种方法让生产者等待所有消费者在返回之前结束他们的循环。最简单的方法是使用另一个通道。我让你考虑一下。

答案 6 :(得分:0)

也可以看看这里:https://github.com/LiangfengChen/goutil/blob/main/concurrent.go

示例可以参考测试用例。

func TestParallelCall(t *testing.T) {
    format := "test:%d"
    data := make(map[int]bool)
    mutex := sync.Mutex{}
    val, err := ParallelCall(1000, 10, func(pos int) (interface{}, error) {
        mutex.Lock()
        defer mutex.Unlock()
        data[pos] = true
        return pos, errors.New(fmt.Sprintf(format, pos))
    })

    for i := 0; i < 1000; i++ {
        if _, ok := data[i]; !ok {
            t.Errorf("TestParallelCall pos not found: %d", i)
        }
        if val[i] != i {
            t.Errorf("TestParallelCall return value is not right (%d,%v)", i, val[i])
        }
        if err[i].Error() != fmt.Sprintf(format, i) {
            t.Errorf("TestParallelCall error msg is not correct (%d,%v)", i, err[i])
        }
    }
}

答案 7 :(得分:-1)

我写了一个简单的程序包来处理Golang的并发性。该软件包将帮助您限制允许并发运行的goroutine的数量: https://github.com/zenthangplus/goccm

示例:

package main

import (
    "fmt"
    "goccm"
    "time"
)

func main()  {
    // Limit 3 goroutines to run concurrently.
    c := goccm.New(3)

    for i := 1; i <= 10; i++ {

        // This function have to call before any goroutine
        c.Wait()

        go func(i int) {
            fmt.Printf("Job %d is running\n", i)
            time.Sleep(2 * time.Second)

            // This function have to when a goroutine has finished
            // Or you can use `defer c.Done()` at the top of goroutine.
            c.Done()
        }(i)
    }

    // This function have to call to ensure all goroutines have finished 
    // after close the main program.
    c.WaitAllDone()
}