Always have x number of goroutines running at any time

前端 未结 7 1747
鱼传尺愫
鱼传尺愫 2020-12-13 00:10

I see lots of tutorials and examples on how to make Go wait for x number of goroutines to finish, but what I\'m trying to do is have ensure there are always x number running

相关标签:
7条回答
  • 2020-12-13 00:43

    Thanks to everyone for helping me out with this. However, I don't feel that anyone really provided something that both worked and was simple/understandable, although you did all help me understand the technique.

    What I have done in the end is I think much more understandable and practical as an answer to my specific question, so I will post it here in case anyone else has the same question.

    Somehow this ended up looking a lot like what OneOfOne posted, which is great because now I understand that. But OneOfOne's code I found very difficult to understand at first because of the passing functions to functions made it quite confusing to understand what bit was for what. I think this way makes a lot more sense:

    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
    }
    
    0 讨论(0)
  • 2020-12-13 00:43

    Grzegorz Żur's answer is the most efficient way to do it, but for a newcomer it could be hard to implement without reading code, so here's a very simple implementation:

    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

    0 讨论(0)
  • 2020-12-13 00:44
    1. Create channel for passing data to goroutines.
    2. Start 20 goroutines that processes the data from channel in a loop.
    3. Send the data to the channel instead of starting a new goroutine.
    0 讨论(0)
  • 2020-12-13 00:49

    I've wrote a simple package to handle concurrency for Golang. This package will help you limit the number of goroutines that are allowed to run concurrently: https://github.com/zenthangplus/goccm

    Example:

    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()
    }
    
    0 讨论(0)
  • 2020-12-13 00:51

    You may find Go Concurrency Patterns article interesting, especially Bounded parallelism section, it explains the exact pattern you need.

    You can use 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) }
    
    0 讨论(0)
  • 2020-12-13 00:54

    This is a simple producer-consumer problem, which in Go can be easily solved using channels to buffer the paquets.

    To put it simple: create a channel that accept your IDs. Run a number of routines which will read from the channel in a loop then process the ID. Then run your loop that will feed IDs to the channel.

    Example:

    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
        }
    }
    

    Things to know:

    • Unbuffered channels are blocking: if the item wrote into the channel isn't accepted, the routine feeding the item will block until it is
    • My example lack a closing mechanism: you must find a way to make the producer to wait for all consumers to end their loop before returning. The simplest way to do this is with another channel. I let you think about it.
    0 讨论(0)
提交回复
热议问题