Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

grpool

Go语言中的goroutine虽然相对于系统线程来说比较轻量级(初始栈大小仅2KB),但是在高并发量下的goroutine频繁创建和销毁对于性能损耗以及GC来说压力也不小。充分将goroutine复用,减少goroutine的创建/销毁的性能损耗,这便是grpoolgoroutine进行池化封装的目的。例如,针对于100W个执行任务,使用goroutine的话需要不停创建并销毁100Wgoroutine,而使用grpool也许底层只需要几万个goroutine便能充分复用地执行完成所有任务。

经测试,goroutine池对于业务逻辑的执行效率(降低执行时间/CPU使用率)提升不大,甚至没有原生的goroutine执行快速(池化goroutine执行调度并没有底层go调度器高效,因为池化goroutine的执行调度也是基于底层go调度器),但是由于采用了复用的设计,池化后对内存的使用率得到极大的降低。

概念: 1.

  1. Poolgoroutine池,用于管理若干可复用的goroutine协程资源;

...

  1. Worker:池对象中参与任务执行的goroutine,一个Worker可以执行若干个Job,直到队列中再无等待的Job

...

  1. Job:添加到池对象的任务队列中等待执行的任务,是一个func()的方法,一个Job同时只能被一个Worker获取并执行;

使用方式

import "github.com/gogf/gf/os/grpool"

...

为什么呢?这里的执行结果无论是采用go关键字来执行还是grpool来执行都是如此。原因是,对于异步线程/协程来讲,函数进行异步执行注册时,该函数并未真正开始执行(注册时只在goroutine的栈中保存了变量i的内存地址),而一旦开始执行时函数才会去读取变量i的值,而这个时候变量i的值已经自增到了10。 清楚原因之后,改进方案也很简单了,就是在注册异步执行函数的时候,把当时变量i的值也一并传递获取;或者把当前变量i的值赋值给一个不会改变的临时变量,在函数中使用该临时变量而不是直接使用变量i。

改进后的示例代码如下:

1)、使用go关键字

https://github.com/gogf/gf/blob/master/.example/os/grpool/grpool3.go

...

注意,异步执行时并不会保证按照函数注册时的顺序执行,以下同理。

2)、使用临时变量

https://github.com/gogf/gf/blob/master/.example/os/grpool/grpool4.go

...

3、最后我们使用程序测试一下grpool和原生的goroutine之间的性能

1)、grpool

package main

import (
    "fmt"
    "sync"
    "time"
    "github.com/gogf/gf/os/gtime"
    "github.com/gogf/gf/os/grpool"
)

func main() {
    start := gtime.Millisecond()
    wg    := sync.WaitGroup{}
    for i := 0; i < 10000000; i++ {
        wg.Add(1)
        grpool.Add(func() {
            time.Sleep(time.Millisecond)
            wg.Done()
        })
    }
    wg.Wait()
    fmt.Println(grpool.Size())
    fmt.Println("time spent:", gtime.Millisecond() - start)
}

2)、goroutine

package main

import (
    "fmt"
    "sync"
    "time"
    "github.com/gogf/gf/os/gtime"
)


func main() {
    start := gtime.Millisecond()
    wg    := sync.WaitGroup{}
    for i := 0; i < 10000000; i++ {
        wg.Add(1)
        go func() {
            time.Sleep(time.Millisecond)
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println("time spent:", gtime.Millisecond() - start)
}

3)、运行结果比较

测试结果为两个程序各运行3次取平均值。

grpool:
    goroutine count: 847313
     memory   spent: ~2.1 G
     time     spent: 37792 ms

goroutine:
    goroutine count: 1000W
    memory    spent: ~4.8 GB
    time      spent: 27085 ms

...