[Go] golang缓冲通道实现资源池

Posted 陶士涵的菜地

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Go] golang缓冲通道实现资源池相关的知识,希望对你有一定的参考价值。

go的pool资源池:
1.当有多个并发请求的时候,比如需要查询数据库
2.先创建一个2个容量的数据库连接资源池
3.当一个请求过来的时候,去资源池里请求连接资源,肯定是空的就创建一个连接,执行查询,结束后放入了资源池里
4.当第二个请求过来的时候,也是去资源池请求连接资源,就直接在池中拿过来一个连接进行查询
5.当并发大的时候,资源池里面没有足够连接资源,就会不停创建新资源,放入池里面的时候,也会放不进去,就主动关闭掉这个资源
6.这里的资源池实质上是一个缓冲通道,里面放着连接资源

 

package main

import (
	"errors"
	"io"
	"log"
	"math/rand"
	"sync"
	"sync/atomic"
	"time"
)

//定义一个结构体,这个实体类型可以作为整体单元被复制,可以作为参数或返回值,或被存储到数组
type Pool struct {
	//定义成员,互斥锁类型
	m sync.Mutex
	//定义成员,通道类型,通道传递的是io.Closer类型
	resources chan io.Closer
	//定义工厂成员,类型是func()(io.Closer,error)
	//error是预定义类型,实际上是个interface接口类型
	factory func() (io.Closer, error)
	closed  bool
}

//定义变量,函数返回的是error类型
var ErrPoolClosed = errors.New("池已经关闭了")

//定义New方法,创建一个池,返回的是Pool类型的指针
//传入的参数是个函数类型func(io.Closer,error)和池的大小
func New(fn func() (io.Closer, error), size uint) (*Pool, error) {
	//使用结构体字面值给结构体成员赋值
	myPool := Pool{
		factory:   fn,
		resources: make(chan io.Closer, size),
	}
	//返回两个返回值
	return &myPool, nil
}

//从池中请求获取一个资源,给Pool类型定义的方法
//返回的值是io.Closer类型
func (p *Pool) Acquire() (io.Closer, error) {
	//基于select的多路复用
	//select会等待case中有能够执行的,才会去执行,等待其中一个能执行就执行
	//default分支会在所有case没法执行时,默认执行,也叫轮询channel
	select {
	case r, _ := <-p.resources:
		log.Printf("请求资源:来自通道 %d", r.(*dbConn).ID)
		return r, nil
	//如果缓冲通道中没有了,就会执行这里
	default:
		log.Printf("请求资源:创建新资源")
		return p.factory()
	}
}

//将一个使用后的资源放回池
//传入的参数是io.Closer类型
func (p *Pool) Release(r io.Closer) {
	//使用mutex互斥锁
	p.m.Lock()
	//解锁
	defer p.m.Unlock()
	//如果池都关闭了
	if p.closed {
		//关掉资源
		r.Close()
		return
	}
	//select多路选择
	//如果放回通道的时候满了,就关闭这个资源
	select {
	case p.resources <- r:
		log.Printf("释放资源:放入通道 %d", r.(*dbConn).ID)
	default:
		log.Printf("释放资源:关闭资源%d", r.(*dbConn).ID)
		r.Close()
	}
}

//关闭资源池,关闭通道,将通道中的资源关掉
func (p *Pool) Close() {
	p.m.Lock()
	defer p.m.Unlock()
	p.closed = true
	//先关闭通道再清空资源
	close(p.resources)
	//清空并关闭资源
	for r := range p.resources {
		r.Close()
	}
}

//定义全局常量
const (
	maxGoroutines = 20 //使用25个goroutine模拟同时的连接请求
	poolSize      = 2  //资源池中的大小
)

//定义结构体,模拟要共享的资源
type dbConn struct {
	//定义成员
	ID int32
}

//dbConn实现io.Closer接口
func (db *dbConn) Close() error {
	return nil
}

var idCounter int32 //定义一个全局的共享的变量,更新时用原子函数锁住
//定义方法,创建dbConn实例
//返回的是io.Closer类型和error类型
func createConn() (io.Closer, error) {
	//原子函数锁住,更新加1
	id := atomic.AddInt32(&idCounter, 1)
	log.Printf("创建新资源: %d", id)
	return &dbConn{id}, nil
}
func main() {
	//计数信号量
	var wg sync.WaitGroup
	//同时并发的数量
	wg.Add(maxGoroutines)
	myPool, _ := New(createConn, poolSize)
	//开25个goroutine同时查询
	for i := 0; i < maxGoroutines; i++ {
		//模拟请求
		time.Sleep(time.Duration(rand.Intn(2)) * time.Second)
		go func(gid int) {
			execQuery(gid, myPool)
			wg.Done()
		}(i)
	}
	//等待上面开的goroutine结束
	wg.Wait()
	myPool.Close()
}

//定义一个查询方法,参数是当前gorotineId和资源池
func execQuery(goroutineId int, pool *Pool) {
	//从池里请求资源,第一次肯定是没有的,就会创建一个dbConn实例
	conn, _ := pool.Acquire()
	//将创建的dbConn实例放入了资源池的缓冲通道里
	defer pool.Release(conn)
	//睡眠一下,模拟查询过程
	time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
	log.Printf("执行查询...协程ID [%d] 资源ID [%d]", goroutineId, conn.(*dbConn).ID)
}

 

  

以上是关于[Go] golang缓冲通道实现资源池的主要内容,如果未能解决你的问题,请参考以下文章

Golang✔️走进 Go 语言✔️ 第十八课 通道关闭 & 工作池

Golang✔️走进 Go 语言✔️ 第十八课 通道关闭 & 工作池

golang fifo 缓冲通道

Go:缓冲通道总和更快?

Go语言的并发(多线程协程)通道(信道)缓冲信道(Buffer Channels)长度和容量

golang 创建一个简单的资源池,重用资源,减少GC负担