golang 实现协程池

go 的 goroutine 提供了一种比线程而言更廉价的方式处理并发场景。相比线程,协程占据更小的内存空间,并且由于是在用户态进行调度,上下文切换的代价更小。所以协程更加容易支撑几万几百万的并发。但 goroutine 太多仍会导致调度性能下降、GC 频繁、内存暴涨, 引发一系列问题。

因此本文的目的是学习如何实现一个go协程池。
借鉴java的线程池,定义如下的结构体

type GoroutinePool struct {
   
	name     string
	coreSize uint32 //定义有多少协程
	taskChan chan func() //类似java的Runable中的run方法
	stop     bool //是否停止协程池
}

新建一个协程池,通过start方法启动协程。
使用select实现任务的执行和协程的销毁

func NewGoroutinePool(name string, coreSize uint32) *GoroutinePool{
   
	goroutinePool := &GoroutinePool{
   
		name:     name,
		coreSize: coreSize,
		taskChan: make(chan func()),
		stop:     false,
	}
	goroutinePool.start()
	return goroutinePool
}

func (pool *GoroutinePool) start() {
   
	for i := uint32(0); i < pool.coreSize; i++ {
   
		go func() {
   
			for {
   
				select {
   
				case task := <-pool.taskChan:
					task()
				default:
					if pool.stop && len(pool.taskChan) == 0{
   
						log.Printf("stop")
						close(pool.taskChan)
						break
					}
				}
			}
		}()
	}
}

提交任务并且执行,使用go的recover()机制,避免panic导致协程终止

func (pool *GoroutinePool) Execute(tasks ...Task) error {
   
	if pool.stop {
   
		return fmt.Errorf("pool is stop")
	}
	for _, t := range tasks {
   
		task := t
		fun := func() {
    pool.exec(task) }
		pool.taskChan <- fun
	}
	return nil
}

func (pool *GoroutinePool) exec(task Task) {
   
	defer func() {
   
		if err := recover(); err != nil {
   
			stacks := pool.getStacks(5, 6)
			log.Printf("%s pool exec panic:%v,stack:%v", pool.name, err, stacks)
		}
	}()

	result, err := task()
	log.Printf("result:%v,err:%v", result, err)
}

停止协程、执行异常时获取堆栈信息

func (pool *GoroutinePool) Stop() {
   
	pool.stop = true
}

func (pool *GoroutinePool) getStacks(skip int, maxNum int) []string {
   
	pc := make([]uintptr, maxNum)
	n := runtime.Callers(skip, pc)
	var stacks []string
	for i := 0; i < n; i++ {
   
		f := runtime.FuncForPC(pc[i])
		if f == nil {
   
			stacks = append(stacks, "unknown Func")
		} else {
   
			file, line := f.FileLine(pc[i])
			stacks = append(stacks, fmt.Sprintf("%v %v %v", f.Name(), file, line))
		}
	}
	return stacks
}

可以看出利用golang的go语法糖和channel机制可以很容易的实现一个协程池。
但是本文实现的协程池还缺少了:

1、协程池大小的动态扩展能力;例如java支持coreSzie和maxSize,允许一定的突发。

2、拒绝策略。

3、使用pool.taskChan <- fun 进行任务的提交,当channel满时,会阻塞业务逻辑。

推荐阅读

1、原来阿里字节员工简历长这样

2、一条SQL差点引发离职

3、MySQL并发插入导致死锁


如果你也觉得我的分享有价值,记得点赞或者收藏哦!你的鼓励与支持,会让我更有动力写出更好的文章哦!

相关推荐

  1. golang 实现

    2024-01-26 13:48:04       65 阅读
  2. Golangants使用笔记

    2024-01-26 13:48:04       64 阅读
  3. go实现

    2024-01-26 13:48:04       49 阅读
  4. golang 动态扩缩容

    2024-01-26 13:48:04       37 阅读
  5. Lua

    2024-01-26 13:48:04       41 阅读
  6. Go实现简单的(通过channel实现

    2024-01-26 13:48:04       42 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-26 13:48:04       106 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-26 13:48:04       116 阅读
  3. 在Django里面运行非项目文件

    2024-01-26 13:48:04       95 阅读
  4. Python语言-面向对象

    2024-01-26 13:48:04       103 阅读

热门阅读

  1. golang map真有那么随机吗?——map遍历研究

    2024-01-26 13:48:04       43 阅读
  2. php/js:实现几秒后进行页面跳转

    2024-01-26 13:48:04       58 阅读
  3. 1/25 work

    1/25 work

    2024-01-26 13:48:04      65 阅读
  4. HTTP简单的接收和发送

    2024-01-26 13:48:04       56 阅读
  5. Spring Cloud Gateway 知识总结

    2024-01-26 13:48:04       58 阅读
  6. Python图像处理【19】基于霍夫变换的目标检测

    2024-01-26 13:48:04       53 阅读
  7. docker mysql8.0.26 迁移 mysql8.3.0

    2024-01-26 13:48:04       46 阅读
  8. LambdaQueryWrapper用法超详细讲解

    2024-01-26 13:48:04       54 阅读
  9. flink中的重启策略

    2024-01-26 13:48:04       58 阅读
  10. c# 访问修饰符

    2024-01-26 13:48:04       53 阅读