[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)
}

  

原文地址:https://www.cnblogs.com/taoshihan/p/10422915.html

时间: 2024-10-17 11:24:17

[Go] golang缓冲通道实现资源池的相关文章

024_go语言中的缓冲通道

代码演示 package main import "fmt" func main() { messages := make(chan string, 2) messages <- "buffered" messages <- "channel" fmt.Println(<-messages) fmt.Println(<-messages) } 代码运行结果 buffered channel 代码解读: 默认通道是无缓冲的,

[golang] channel通道

说明 channel是go当中的一个核心类型,可以看做是管道.并发核心单元可以通过channel进行数据的发送和接收,从而实现通信. 在go中,channel是一种数据类型,主要被用来解决协程的同步问题以及协程之间数据共享(数据传递)的问题. go当中的goroutine运行在相同的地址空间,因此访问共享内存地址必须做好同步,goroutine奉行通过通信来共享内存,而不是共享内存来通信. 引用类型channel可用于多个goroutine通讯,在其内部实现了同步,确保并发安全. 定义chann

golang编程实践总结

一.语法基础 main函数在main包,每个代码文件中的init函数都将在main函数执行前调用. 同一文件夹中代码使用同一包名,且一般包与文件夹名一致. 同一包中的变量和类型.标识符可直接使用,包名类型命名空间,包中每个文件对应一个独立的职责. 将包导入其他文件中,可通过包名间接访问首字母大写的标识符,所定义的对象也只能访问首字母开头的成员变量或函数. 所有变量被初始化的零值:数值(0),字符串(""),布尔(false),指针(nil),引用类型(返回nil作为其值,但引用的底层数

Golang 入门系列(十五)如何理解go的并发?

前面已经讲过很多Golang系列知识,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863.html, 接下来要说的是golang的并发,其实之前简单介绍过协程(goroutine)和管道(channel) 等基础内容,只是比较简单,只讲了基本的语法.今天就详细说说golang的并发编程. 一.并发和并行 Go是并发语言,而不是并行语言.所以我们在讨论,我们首先必须了解什么是并发,以及它与并行性有什么不同. 什么

Go语言之通道

上一篇我们讲的原子函数和互斥锁,都可以保证共享数据的读写.但是呢,它们还是有点复杂,而且影响性能.对此,Go又为我们提供了一种工具,这就是通道. 所以在多个goroutine并发中,我们不仅可以通过原子函数和互斥锁保证对共享资源的安全访问,消除竞争的状态,还可以通过使用通道,在多个goroutine发送和接受共享的数据,达到数据同步的目的. 通道,它有点像在两个routine之间架设的管道:一个goroutine可以往这个管道里塞数据,另外一个可以从这个管道里取数据.有点类似于我们说的队列. 声

go 通道

1. package main import "fmt" func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } c <- sum // 把 sum 发送到通道 c } func main() { s := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(s[len(s)/2:], c) go sum(s[:len(s)/2], c)

gopl goroutine 和通道

Go 语言的并发编程风格 Go 有两种并发编程风格: goroutine 和通道(chennle),支持通信顺序进程(Communicating Sequential Process, CSP),CSP 是一个并发的模式,在不同的执行体(goroutine)之间传递值,但是变量本身局限于单一的执行体. 共享内存多线程的传统模型,这和在其他主流语言中使用的线程类似. 这章讲第一种 goroutine 和通道. 通道 如果说 goroutine 是 Go 程序并发的执行体,通道就是它们之间的连接.每

Go非缓冲/缓冲/双向/单向通道

1. 非缓冲和缓冲 package main import ( "fmt" "strconv" ) func main() { /* 非缓冲通道:make(chan T) 一次发送,一次接收,都是阻塞的 缓冲通道:make(chan T , capacity) 发送:缓冲区的数据满了,才会阻塞 接收:缓冲区的数据空了,才会阻塞 */ ch1 := make(chan int) //非缓冲通道 fmt.Println(len(ch1), cap(ch1)) //0 0

Go 函数特性和网络爬虫示例

爬取页面 这篇通过网络爬虫的示例,来了解 Go 语言的递归.多返回值.延迟函数调用.匿名函数等方面的函数特性.首先是爬虫的基础示例,下面两个例子展示通过 net/http 包来爬取页面的内容. 获取一个 URL 下面的程序展示从互联网获取信息,获取URL的内容,然后不加解析地输出: // 输出从 URL 获取的内容 package main import ( "fmt" "io" "net/http" "os" "s