前言

当初很多公司都在陆续的搭建golang的语言栈,大家有没有想过为什么会呈现这种状况?一是因为go比拟适宜做中间件,还有一个起因就是go的并发反对比拟好,也就是咱们平时所谓的高并发,并发反对离不开协程,当然协程也不是乱用的,须要治理起来,治理协程的形式就是协程池,所以协程池也并没有那么神秘,明天咱们就来一步一步的揭开协程池的面纱,如果你没有接触过go的协程这块的话也没有关系,我会尽量写的具体。

# goroutine(协程)
先来看一个简略的例子

func go_worker(name string) {
   for i := 0; i < 5; i++ {
       fmt.Println("我的名字是", name)
       time.Sleep(1 * time.Second)
   }
   fmt.Println(name, "执行结束")
}
func main() {
    go_worker("123")
    go_worker("456")
   for i := 0; i < 5; i++ {
       fmt.Println("我是main")
       time.Sleep(1 * time.Second)
   }
}

咱们在执行这段代码的时候,当然是依照程序执行

go_worker(“123”)->go_worker(“456”)->我是main执行

输入后果如下

我的名字是 123
我的名字是 123
我的名字是 123
我的名字是 123
我的名字是 123
123 执行结束
我的名字是 456
我的名字是 456
我的名字是 456
我的名字是 456
我的名字是 456
456 执行结束
我是main
我是main
我是main
我是main
我是main

这样的执行是并行的,也就是说必须得等一个工作执行完结,下一个工作才会开始,如果某个工作比较慢的话,整个程序的效率是可想而知的,然而在go语言中,反对协程,所以咱们能够把下面的代码革新一下

func go_worker(name string) {
   for i := 0; i < 5; i++ {
       fmt.Println("我的名字是", name)
       time.Sleep(1 * time.Second)
   }
   fmt.Println(name, "执行结束")
}
func main() {
   go go_worker("123")  //协程
   go go_worker("456")  //协程
   for i := 0; i < 5; i++ {
       fmt.Println("我是main")
       time.Sleep(1 * time.Second)
   }
}

咱们在不同的go_worker后面加上了一个go,这样所有工作就异步的串行了起来,输入后果如下

我是main
我的名字是 456
我的名字是 123
我的名字是 123
我是main
我的名字是 456
我是main
我的名字是 456
我的名字是 123
我是main
我的名字是 456
我的名字是 123
我的名字是 456
我的名字是 123
我是main

大家能够看到这样的话就是各自工作执行各自的事件,相互不影响,效率也失去了很大的晋升,这就是goroutine

channel(管道)

有了协程之后就会带来一个新的问题,协程之间是如何通信的?于是就引出了管道这个概念,管道其实很简略,无非就是往里放数据,往外取数据而已

func worker(c chan int) {
   num := <-c  //读取管道中的数据,并输入
   fmt.Println("接管到参数c:", num)
}
func main() {
   //channel的创立,须要执行管道数据的类型,咱们这里是int
   c := make(chan int)
   //开拓一个协程 去执行worker函数
   go worker(c)
   c <- 2  //往管道中写入2
   fmt.Println("main")
}

咱们能够看到上述例子,在main函数中,咱们定义了一个管道,为int类型,而且往里面写入了一个2,而后在worker中读取管道c,就能获取到2

# 协程会引发的问题

既然golang中开启协程这么不便,那么会不会存在什么坑呢?

咱们能够看上图,理论业务中,不同的业务都开启不同的goroutine来执行,然而在cpu宏观层面上来讲,是串行的一个指令一个指令去执行的,只是执行的十分快而已,如果指令来的太多,cpu的切换也会变多,在切换的过程中就须要耗费性能,所以协程池的次要作用就是治理goroutine,限定goroutine的个数

协程池的实现
//定义工作Task类型,每一个工作Task都能够形象成一个函数
type Task struct{
 f func() error //一个task中必须蕴含一个具体的业务
}


//通过NewTask来创立一个Task
func NewTask(arg_f func() error) *Task{
 t := Task{
     f:arg_f,
 }
 return &t
}


//Task也须要一个执行业务的办法
func (t *Task) Execute(){
 t.f()//调用工作中曾经绑定好的业务办法
}
//定义池类型
type Pool struct{
 EntryChannel chan *Task
 WorkerNum int
 JobsChanel chan *Task
}
//创立一个协程池
func NewPool(cap int) *Pool{
 p := Pool{
     EntryChannel: make(chan *Task),
     JobsChanel: make(chan *Task),
     WorkerNum: cap,
 }
 return &p
}
//协程池创立worker并开始工作
func (p *Pool) worker(workerId int){
  //worker一直的从JobsChannel外部工作队列中拿工作
  for task := range p.JobsChanel{
      task.Execute()
      fmt.Println("workerId",workerId,"执行工作胜利")
  }
}
EntryChannel获取Task工作
func (p *Pool) ReceiveTask(t *Task){
 p.EntryChannel <- t
}
//让协程池开始工作
func (p *Pool) Run(){
 //1:首先依据协程池的worker数量限定,开启固定数量的worker
 for i:=0; i<p.WorkerNum; i++{
     go p.worker(i)
 }
 //2:从EntryChannel协程出入口取内部传递过去的工作
 //并将工作送进JobsChannel中
 for task := range p.EntryChannel{
     p.JobsChanel <- task
 }
 //3:执行结束须要敞开JobsChannel和EntryChannel
 close(p.JobsChanel)
 close(p.EntryChannel)
}
//创立一个task
 t:= NewTask(func() error{
     fmt.Println(time.Now())
     return nil
 })

 //创立一个协程池,最大开启5个协程worker
 p:= NewPool(3)
 //开启一个协程,一直的向Pool输送打印一条工夫的task工作
 go func(){
     for {
         p.ReceiveTask(t)//把工作推向EntryChannel
     }
 }()
 //启动协程池p
 p.Run()