【Golang】莫非这就是Go最佳协程池

在这里插入图片描述

 经常写Go的小伙伴都知道,Go语言的goruntine是这门编程语言的一大利器,相比线程,基于协程的goruntine更加轻量和高效,并且在语法上十分的简单。

为什么协程比线程更高效?

 协程(Coroutine)和线程(Thread)是两种不同的并发执行控制结构,它们在多个方面存在显著的差异。

 首先在定义上,协程是程序级别的执行单元,是轻量级的。线程是操作系统级别的并发执行单元,是重量级的。

协程的堆栈是动态的,可以根据需要增长和缩小,内存使用效率高。线程有自己的固定大小的堆栈,堆栈大小限制了线程的数量,并可能导致堆栈溢出错误。协程的创建和切换成本非常低,因为它们是在用户级别管理的,不需要系统级的上下文切换。线程的创建和切换成本相对较高,因为它们由操作系统管理,并涉及到系统级的上下文切换。

举个例子,你要给一个人的银行卡账户里转账,协程就好比电子(网上)银行卡,线程就好比ATM机,你使用网银转账的话可以绑定某宝或某信直接动动手指就能转账,但是去ATM机的话就想对麻烦一些,但本质上都是会通过银行的信息系统完成最终的转账,只是不同方式的操作成本不同。

在这里插入图片描述

 协程我们说完了,但是如果想更好的使用协程高并发处理任务的话并不是一件容易的事情,我们经常会了解多线程这个概念,那么在实际的场景中,这个多线程的多到底是多少才合适?这就是一个比较值得思考的问题。为了能更好的利用线程,慢慢的演化出了池化思想,没错,就是线程池,而到了协程,这个思想依旧通用,下面就来分享一下本篇文章的重点:Go语言中优雅的使用协程池。

为什么演进出了池化思想?

 池化思想的演进主要源于对资源高效利用和系统性能优化的需求,它是一种将资源(如线程、数据库连接等)预先创建并统一管理,以实现资源高效利用和降低开销的策略。

在这里插入图片描述

  • 减少资源创建与销毁的开销:在多线程编程中,线程的创建和销毁是资源密集型的操作,需要消耗大量的时间和系统资源。通过线程池,可以预先创建并维护一组可复用的线程,避免了频繁创建和销毁线程的开销。

  • 提升系统响应速度:池化技术通过预分配资源,使得系统在面对任务时能够迅速响应,无需等待资源的创建。这提高了系统的响应速度和吞吐量。

Go优雅的协程池:Ants

 如果想让项目中的goruntine使用的更加高效,协程池似乎是一个必备的工具,因为Go语言非常的简单,自己手写一个协程池也并非难事,但是做为一名还未达到顶尖水平的Gopher,学习他人的优秀代码是一个需要经历的过程,众所周知,程序员的日常只有三件事:学习!学习!还是特么的学习!

 话不多说,本次分享的Ants是一个非常好用的Go协程池包,它的Github地址:github.com/panjf2000/ants

 使用前需要先下载依赖:

go get -u github.com/panjf2000/ants/v2

 然后我们只实现一个功能:计数,就是将一个名为num的变量从1加到10000,假设每次操作耗时1ms

(1) 单线程

 单线程场景下:

var num int32

func addNum(i int32) {
	atomic.AddInt32(&num, i)
	time.Sleep(time.Millisecond)
	fmt.Println("now num =", num)
}

func TestNum(t *testing.T) {
	runTimes := 10000
	for i := 0; i < runTimes; i++ {
		addNum(1)
	}
	fmt.Printf("result num = %d \n ", num)
}

 看下执行耗时,竟然执行了156秒:

result num = 10000 
 --- PASS: TestNum (156.18s)
PASS

(2) 初步使用Ants

 现在我们启用Ants:

func TestAnt(t *testing.T) {
	defer ants.Release()

	var wg sync.WaitGroup
	syncCalculateSum := func() {
		addNum(1)
		wg.Done()
	}

	runTimes := 10000

	for i := 0; i < runTimes; i++ {
		wg.Add(1)
		_ = ants.Submit(syncCalculateSum) //需要执行的方法
	}

	wg.Wait()

	fmt.Printf("running goroutines: %d\n", ants.Running())
	fmt.Printf("num = %d \n ", num)
}

 执行结果,竟然只有0.31秒!

running goroutines: 8376
num = 10000 
 --- PASS: TestAnt (0.31s)
PASS

 但它的弊端是竟然同时使用了8376个协程,同时使用大量资源对服务器无非是一次大的考验,因此不太优雅。

(3) 使用Ants控制协程数量

 想要优雅,我们可以使用有限协程数量的协程池,比如定个小目标,先用它100个:

func TestAntPool(t *testing.T) {
	defer ants.Release()

	var wg sync.WaitGroup
	f := func() {
		addNum(1)
		wg.Done()
	}
	runTimes := 10000
	pool, _ := ants.NewPool(100)

	for i := 0; i < runTimes; i++ {
		wg.Add(1)
		_ = pool.Submit(f)
	}
	wg.Wait()

	fmt.Printf("running goroutines: %d\n", pool.Running())
	fmt.Printf("num = %d \n ", num)
}

 执行结果,只有1.21秒,虽然耗时长了一点,但是优雅了许多!

running goroutines: 100
num = 10000 
 --- PASS: TestAntPoolAndWithPanicHandler (1.21s)
PASS

(4) Ants的另一种使用形式

 除此之外,Ants还支持另一种形式,执行具体的函数:

func TestAntWithFunc(t *testing.T) {
	defer ants.Release()

	runTimes := 10000

	var wg sync.WaitGroup

	p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
		addNum(i.(int32))
		wg.Done()
	})
	defer p.Release()

	for i := 0; i < runTimes; i++ {
		wg.Add(1)
		_ = p.Invoke(int32(1)) //这个地方参数可以传结构体
	}
	wg.Wait()
	fmt.Printf("running goroutines: %d\n", p.Running())
	fmt.Printf("finish all tasks, result is %d\n", num)
}

 此外,Ants还提供了NewMultiPool类,初始化多个协程池,可以根据预先定义的策略:轮询或者最少使用策略,从多个协程池中获取worker。

func TestMultiPool(t *testing.T) {
	defer ants.Release()

	runTimes := 10000

	var wg sync.WaitGroup

	f := func() {
		addNum(1)
		wg.Done()
	}

	//10表示初始化10个协程池,-1位置参数表示协程池的容量,值为-1时代表不限制容量
	mp, _ := ants.NewMultiPool(10, -1, ants.RoundRobin)
	defer func() {
		_ = mp.ReleaseTimeout(5 * time.Second)
	}()
	for i := 0; i < runTimes; i++ {
		wg.Add(1)
		_ = mp.Submit(f)
	}

	wg.Wait()
	fmt.Printf("running goroutines: %d\n", mp.Running())
	fmt.Printf("finish all tasks, result is %d\n", num)
}

 好了,关于Ants的使用我们先分享到这里~

协程池的应用场景

 协程池并不是在任何时候任何业务里都适用,它也是有一些典型的应用场景的,比如:

  • 高并发处理:当系统需要处理大量并发请求时,使用协程池可以有效地管理并发任务,避免资源耗尽,提高系统稳定性和性能。

  • 资源密集型任务:对于需要较长时间执行或占用较多系统资源的任务,使用协程池可以控制并发数量,避免系统资源被过度占用。

  • 任务调度和负载均衡:在需要对任务进行调度和负载均衡的场景中,协程池可以提供有效的任务排队和调度机制,确保任务能够按照预定的策略执行。

 综上所述,Go语言的协程池在并发编程中具有重要的作用,通过合理使用协程池,可以优化系统资源的使用,提高并发性能和吞吐量,同时简化并发编程的复杂性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lua语言协程的使用情况: Lua语言协程可以通过 coroutine 模块的 coroutine.create() 函数来创建。在Lua中,协程是一种轻量级线程,可以用于相互协作的任务。协程通过yield函数可以让出CPU时间,使得其他协程可以继续执行。Lua语言协程的应用场景比较广泛,主要体现在以下方面: 1. 网络平台:通常用于处理大量的客户端连接。将每个客户端连接封装为一个协程,并采用事件循环的方式,让协程间相互协作,营造出一种类似于多线程的效果。 2. Web开发:在Web开发中,多个请求可以封装成协程,使用协程可以更好地实现异步I/O操作,提升程序性能。 3. 游戏开发:常常使用协程来实现游戏中的异步I/O操作,比如异步加载资源等。 Java语言中线程的使用情况: Java语言中线程是一个非常重要的概念,它在JVM层面提供了对多线程的支持。线程在Java中可以通过Thread类创建。Java在实现线程时,采用多线程并发的思想,以提高程序的执行效率和并发性。Java中线程的使用场景主要有下面几个方面: 1. 面向图形用户界面(GUI)编程:在GUI编程中,Java中的UI线程通常被用来响应用户的交互请求,这样可以提高应用的响应速度。 2. Web开发:针对Web开发,Java的线程可以通过线程来提供多线程并发支持。同时,可以使用Java的多线程技术实现请求的异步处理。 3. 大数据处理:在大数据处理中,Java多线程技术通常被用于分布式计算和并行化处理,以提高计算效率。 golang语言协程的使用情况: go语言采用轻量级的协程并发模型,称为Goroutine模型。在Goroutine模型中,每个协程都只需极少的内存,并且调度器可以自动地对协程进行管理,从而大大降低了协程的使用成本。golang语言协程的应用场景主要体现在以下方面: 1. 网络编程:在网络编程中,采用协程可以避免用户态和内核态的切换,这样能够提高程序的性能和执行效率。 2. 高并发处理:在高并发场景下,协程能够以非常低的成本实现并发操作,从而提高程序的并发性和执行速度。 3. 分布式处理:协程的轻量级特性可以使得其在分布式处理中得到广泛应用,例如golang中的map-reduce处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值