• Go:关于goroutine及ants的思考



    gorountine的优势

    在开发项目之前之所以使用go语言是因为Go天生支持高并发,只需要go func()就可以实现一个用户态的协程,占用的资源非常小仅仅2k左右(并且支持动态扩容),而正常采用java,c++等语言启用的线程一般都是内核态的占用的内存资源一般在4m左右,而假设我们的服务器CPU内存为4G,那么很明显才用的内核态线程的并发总数量也就是1024个,相反查看一下Go语言的协程则可以达到410241024/2=200w.这么一看就明白了为什么Go语言天生支持高并发;这样一看我们发现根本不需要考虑协程池的使用,直接go func一直启用就好了,但是万事难料,我们举一个很不好的例子那就是大量的协程创建销毁需要消耗cpu性能,为了减少这些不必要的性能损失,同时可以支持高并发我们必须想办法再去减少一些不必要的cup性能损耗。 例如Java为了减少这种不必要的线程创建销毁的性能损失,官方包提供相关的线程池方案一共有四种:

    • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
    • newScheduledThreadPool 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
    • newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

    gorountine线程池方案:ants

    go语言也有相关的协程池方案,而且使用最广泛同时性能最稳定的就是ants。

    示例:

    var tunnel = make(chan string, 1) // 取决于函数业务的参数类型
     
    func Test66(t *testing.T) {
    	go IncomingZombie()
     
    	chairPool, _ := ants.NewPoolWithFunc(3, ExecuteZombie) // 声明有几把电椅
    	defer chairPool.Release()
     
    	for {
    		select {
    		case a := <-tunnel:
    			go chairPool.Invoke(a) //将参数传输给执行函数
    			fmt.Println("当前执行的协程数: ", chairPool.Running())
    		}
    	}
    }
     
    // 处决僵尸
    func ExecuteZombie(i interface{}) {
    	fmt.Printf("正在处决僵尸 %s 号,还有5秒钟....\n", i.(string))
    	time.Sleep(1 * time.Second)
    	fmt.Printf(":) %s 玩完了,下一个\n-----------------\n", i.(string))
    }
     
    // 僵尸不断进来
    func IncomingZombie() {
    	for i := 0; i < 4; i++ {
    		tunnel <- strconv.Itoa(i)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    执行效果:

    === RUN   Test66
    当前执行的协程数:  0
    当前执行的协程数:  0
    当前执行的协程数:  0
    正在处决僵尸 1 号,还有5秒钟....
    正在处决僵尸 2 号,还有5秒钟....
    正在处决僵尸 0 号,还有5秒钟....
    当前执行的协程数:  3
    :) 2 玩完了,下一个
    -----------------
    :) 0 玩完了,下一个
    -----------------
    正在处决僵尸 3 号,还有5秒钟....
    :) 1 玩完了,下一个
    -----------------
    :) 3 玩完了,下一个
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    原生goroutine与ants的比较

    既然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 并发具有更高的性能
    在这里插入图片描述

    小结

    参考: https://www.cnblogs.com/beginnerzyh/p/16221005.html

  • 相关阅读:
    深度学习跟踪DLT (deep learning tracker)
    synchronized有几种用法?
    Spring源码之六-onRefresh()方法
    上海各梯队IB学校怎么选?
    【UML】要点
    能不能手写Vue响应式?前端面试进阶
    【Java小项目】--- 飞机大战(源码+注释)
    打造顶尖微服务项目!解锁四种持久化工具的酸爽奇迹!
    实用API管理平台推荐:Apipost
    重置Linux虚拟机密码
  • 原文地址:https://blog.csdn.net/zhanggqianglovec/article/details/128002787