前言
在boot4go-fastnet的开发和相关的文章中,就曾经介绍到了,为了尽可能的压缩到执行效率的极致,fastnet不仅没有直接使用golang的net库和golang原生的协程管理,就是为了把协程的调度的开销压缩到极致,今天的这篇文章就是简要的讲讲提供boot4go-fastnet里进行协程管理的一个子项目boot4go-routine库
跟我一起来Routuine
上图是golang里协程的结构图,协程是golang里一个非常重要的底层概念, 要做出一款优秀的高并发高性能的程序,协程是必不可少的。
做过Java程序的开发人员,都知道Thread,即线程,当然,如果长期做CRUD的兄弟们,就很少用到这个了。 协程比Thread更细粒度的处理单元,熟悉JVM的同学应该知道,申请一个Thread,会在JVM里获取那些JVM内存单元里的资源,除开单独Thread里自己处理业务的对象所占用的内存空间,一般一个JVM里的Thread大致会开销掉2-4M左右的内存空间,而golang里通过MGP调度模型更加细化了协程来作为一个处理逻辑单元,更详细的有关golang里的内存调度模型可以网上搜索有关MGP的信息, 正是如此,golang通过协程的封装提供了进行多并发的一种开销非常小的编程模式。 线程的申请开销非常的小,除开自己定义的数据单元,一般协程的开销在2-3K之间,这就和Java的线程相差了很大的差距,除此之外,线程在CPU分任务执行过程中进行调度时,上下文的切换也是非常占用开销的,而协程的切换是在线程内部进行调度,相比之下开销要小的多。这也是为什么很多golang的开发人员,一旦使用golang进行底层开发就爱不释手的原因之一了。
协程池,为了进一步压缩协程的开销
池化的概念,作为开发人员都很熟悉,比如我们经常听到的进程池,数据连接池,htp连接池,缓存池,任务池,池基本上都是把引用或者连接上会有一定开销的对象进行缓存的一种优化方式,这样下次需要对象的时候直接在池里进行获取,这样就达到了优化的目的, 协程池也是一样的道理,需要进行协程处理逻辑单元的时候,不需要通过go func()这样的写法来申请一个新的协程,而是把需要提交的逻辑处理单元丢入已经建立的协程池里,使用已经池化好的协程单元,这样既完全节省了创建的开销,也尽可能的减小了切换的开销。 这就是golang里协程池的实现意图
在boot4go-fastnet项目里就是用了协程池的方式,将从每个客户端建立好以后,需要进行通信的处理逻辑单元放入到协程池里进行处理,boot4go-fastnet的目标是建立高并发情况下依然能够提供高性能的网络通信的服务,所以加入协程池进行处理的意思非常重大,
在github里搜索boot4go-routine,就可以查找到,fastnet里使用的协程池的实现
说了还要练
看看如何使用boot4o-routine的协程池
wp, err := NewPool[int]()
这样就实例化一个最简单的协程池
var wg sync.WaitGroup
taskCount := 100
wg.Add(taskCount)
for idx := 0; idx < taskCount; idx++ {
go func(i int) {
wp.Submit(WorkerFunc(func() error {
fmt.Println(i)
done++
lock.Lock()
order := len(doneMap)
doneMap[i] = order + 1
orderMap[order+1] = i
lock.Unlock()
wg.Done()
return nil
}))
}(idx)
}
往协程池里提交需要处理的逻辑单元,
在上面的代码中可以看到,逻辑处理单元里,加入了Lock,Unlock的代码,这都是进行并发控制的处理代码, 每个协程都是并行进行处理的,如果数据存在着并发的可能性,那么需要在逻辑处理单元里自行进行并发的控制。
结尾
以上就是简单的fastnet里协程池里的来历,在boot4o-routine里,除了协程池这种高效处理并发的方式实现,还提供了多种不同场景里提高并发处理的对象, 比如在boot4go-fastnet进行数据输出到客户端的高性能并发处理的CycleDistributionEventChannel的处理单元,这个的场景是针对大量的客户端输出的场景下可以使用的,我给他命名可循环分布事件通道。 有时间再可以砍砍这个处理单元的诞生意义,为什么在有协程池这样的实现下,还要使用另外的这样的方式。