Hi, I’m Mingo.

In their learning Golang this time, I wrote a detailed study notes on my personal number WeChat public Go programming time, for the language, I was a beginner, so writing should fit in with the new to classmates, if you are just learning the language, don’t focus on prevention, study together, Grow together.

My Github:github.com/iswbm/GolangCodingTime my online blog: golang.iswbm.com


Creating a coroutine in Golang is incredibly simple. You just define a function and execute it using the go keyword.

If you’re working with other languages, you’ll notice that when you’re working with threads, it’s common to use thread pools to reuse threads in order to reduce the overhead of creating and destroying them frequently.

Pooling technology leverages reuse to improve performance. Do you need coroutine pooling in Golang?

In Golang, goroutine is a lightweight thread that is created and scheduled in user mode and does not need to enter the kernel, which means that the overhead of creating and destroying coroutines is minimal.

Therefore, I think most of the time, developers don’t need to use coroutine pools.

But there may be some situations where this is necessary, because I haven’t met them yet.

Leaving aside the question of whether it is necessary, from a purely technical point of view, how can we implement a generic coroutine pool?

Now let’s learn how to write it

First, define a coroutine Pool structure that contains two properties, both of type CHAN.

One is work, which is used to receive tasks

One is SEM, which is used to set the coroutine pool size, the number of coroutines that can be executed simultaneously

Type Pool struct{work chan func() // task sem chan struct{}Copy the code

We then define a New function to create a coroutine pool object, with one detail to note

Work is an unbuffered channel

Sem is a buffer channel, and size is the size of the coroutine pool

func New(size int) *Pool {
    return &Pool{
        work: make(chan func()),
        sem:  make(chan struct{}, size),
    }
}Copy the code

Finally, bind two functions to the coroutine pool object

1. NewTask: Adds a task to the coroutine pool

When NewTask is first called to add a task, since Work is an unbuffered channel, it must go to the second case branch: start a coroutine using the Go worker.

func (p *Pool) NewTask(task func()) { 
    select {
        case p.work <- task:
        case p.sem <- struct{}{}:
            go p.worker(task)
    }
}Copy the code

2. Worker: Used to perform tasks

To enable the reuse of coroutines, this uses the for infinite loop, so that the coroutine does not quit after completing the task, but receives new tasks all the time.

func (p *Pool) worker(task func()) { 
    defer func() { <-p.sem }()
    for {
        task()
        task = <-p.work
    }
}Copy the code

These two functions are the key functions for the implementation of the coroutine pool, and the logic in them is worth studying:

1, if the number of coroutines set is greater than 2, the second time the task is passed to NewTask, select case, if the first coroutine is still running, it must go to the second case, create a new coroutine execution task

2. If the number of incoming tasks is greater than the set number of coroutine pool, and all tasks are still running, then call NewTask to pass in task at this time, neither case will be hit, and the work channel in worker function will be blocked until some tasks are completed, and the NewTask can be received by the work channel in worker function. Continue execution.

This is the implementation process of coprogramming pool.

It’s also easy to use, as you’ll see in the code below

func main()  {
    pool := New(128)
    pool.NewTask(func(){
        fmt.Println("run task")
    })
}Copy the code

To show you the effect, I set the number of coroutine pools to 2, start four tasks, all of which are sleep for 2 seconds, and print the current time.

func main() { pool := New(2) for i := 1; i <5; I++ {pool.newtask (func(){time.sleep (2 * time.second) fmt.println (time.now ())})} // ensure that all coroutines are executed time.Second) }Copy the code

The execution result is as follows. It can be seen that there are altogether 4 tasks. Since the size of the coroutine pool is 2, the 4 tasks are executed in two batches (as can be seen from the printing time).

2020-05-24 23:18:02.014487 +0800 CST M =+ 2.00514524 2020-05-24 23:18:02.014487 +0800 CST M =+2.005243650 2020-05-24 2020-05-24 23:18:04.019819 +0800 CST m=+4.010499440Copy the code