既然Go调度器已经这么优秀了,我们为什么还要使用ants呢?优秀不代表完美,基于G-P-M的Go调度器背后,go程序的并发编程中,可以任性地起大规模的goroutine来执行任务,官方也宣称用golang写并发程序的时候随便起个成千上万的goroutine毫无压力。

    然而,你起1000个goroutine没有问题,10000也没有问题,10w个可能也没问题;那,100w个呢?1000w个呢?(这里只是举个极端的例子,实际编程起这么大规模的goroutine的例子极少)这里就会出问题,什么问题呢?

    首先,即便每个goroutine只分配2KB的内存,但如果是恐怖如斯的数量,聚少成多,内存暴涨,就会对GC造成极大的负担,写过java的同学应该知道jvm GC那万恶的STW(Stop The World)机制,也就是GC的时候会挂起用户程序直到垃圾回收完,虽然Go1.8之后的GC已经去掉了STW以及优化成了并行GC,性能上有了不小的提升,但是,如果太过于频繁地进行GC,依然会有性能瓶颈;
    其次,还记得前面我们说的runtime和GC也都是goroutine吗?是的,如果goroutine规模太大,内存吃紧,runtime调度和垃圾回收同样会出问题,虽然G-P-M模型足够优秀,韩信点兵,多多益善,但你不能不给士兵发口粮(内存)吧?巧妇难为无米之炊,没有内存,Go调度器就会阻塞goroutine,结果就是P的Local队列积压,又导致内存溢出,这就是个死循环…,甚至极有可能程序直接Crash掉,本来是想享受golang并发带来的效益,结果却得不偿失。
    ants是一个高性能的 goroutine 池,实现了对大规模 goroutine 的调度管理、goroutine 复用,允许使用者在开发并发程序的时候限制 goroutine 数量,复用资源,达到更高效执行任务的效果。

    在大规模批量并发任务场景下比原生 goroutine 并发具有更高的性能:

    ants - 图1

    ants流程图,逻辑还是比较简单的:

    ants - 图2

    ants用做端口扫描(原生的goroutine 扫描结果不稳定不可复现)

    package main
    
    import (
        "fmt"
        "github.com/panjf2000/ants/v2"
        "net"
        "sync"
        "time"
    )
    
    func scan(port interface{}) {
        address := fmt.Sprintf("176.36.129.182:%d", port)
        con, err := net.DialTimeout("tcp", address, 3*time.Second) 
        if err != nil {
            return
        }
        fmt.Println(port)
        con.Close()
    }
    
    func main() {
        start:=time.Now().Unix()
        var wg sync.WaitGroup
    
        p, _ := ants.NewPoolWithFunc(10000, func(i interface{}) { // 开启大小为10000的goroutine池同时绑定需要并发执行的函数 相比ants.NewPool,由于是执行单一的函数效率更高
            scan(i)
            wg.Done()
        })
        defer p.Release() //释放goroutine池
    
        for i := 0; i < 65535; i++ {
            wg.Add(1)
            _ = p.Invoke(i) //提交任务 i作为参数传上去
        }
        wg.Wait()
        fmt.Println(time.Now().Unix()-start)
    }

    来源:
    作者:beginnerzyh
    链接:https://www.cnblogs.com/beginnerzyh/p/16221005.html