golang基于通道channel实现一个通用连接池 pool

suoniao 2021-01-20
需要:0索币

golang的channel除了goroutine通信之外还有很多其他的功能,本文将实现一种基于channel的通用连接池。

更新

  • 添加超时处理,需要实现GetActiveTime方法获取最后活跃时间。

何为通用?

连接池的实现不依赖具体的实例,而依赖某个接口,本文的连接池选用的是io.Closer接口,只要是实现了该接口的对象都可以被池管理。
当然,你可以实现基于interface{}的连接池,这样任何对象都可以被管理。

实现原理

将连接句柄存入channel中,由于缓存channel的特性,获取连接时如果池中有连接,将直接返回,如果池中没有连接,将阻塞或者新建连接(没超过最大限制的情况下)。

由于面向接口编程,所有创建连接的逻辑是不清楚的,这里需要传入一个函数,该函数返回一个io.Closer对象。


实现

由于并发问题,在需要操作池中互斥数据的时候需要加锁。

package pool

import (
    "errors"
    "io"
    "sync"
    "time"
)

var (
    ErrInvalidConfig = errors.New("invalid pool config")
    ErrPoolClosed    = errors.New("pool closed")
)

type Poolable interface {
    io.Closer
    GetActiveTime() time.Time
}

type factory func() (Poolable, error)

type Pool interface {
    Acquire() (Poolable, error) // 获取资源
    Release(Poolable) error     // 释放资源
    Close(Poolable) error       // 关闭资源
    Shutdown() error            // 关闭池
}

type GenericPool struct {
    sync.Mutex
    pool        chan Poolable
    maxOpen     int  // 池中最大资源数
    numOpen     int  // 当前池中资源数
    minOpen     int  // 池中最少资源数
    closed      bool // 池是否已关闭
    maxLifetime time.Duration
    factory     factory // 创建连接的方法
}

func NewGenericPool(minOpen, maxOpen int, maxLifetime time.Duration, factory factory) (*GenericPool, error) {
    if maxOpen <= 0 || minOpen > maxOpen {
        return nil, ErrInvalidConfig
    }
    p := &GenericPool{
        maxOpen:     maxOpen,
        minOpen:     minOpen,
        maxLifetime: maxLifetime,
        factory:     factory,
        pool:        make(chan Poolable, maxOpen),
    }

    for i := 0; i < minOpen; i++ {
        closer, err := factory()
        if err != nil {
            continue
        }
        p.numOpen++
        p.pool <- closer
    }
    return p, nil
}

func (p *GenericPool) Acquire() (Poolable, error) {
    if p.closed {
        return nil, ErrPoolClosed
    }
    for {
        closer, err := p.getOrCreate()
        if err != nil {
            return nil, err
        }
        // 如果设置了超时且当前连接的活跃时间+超时时间早于现在,则当前连接已过期
        if p.maxLifetime > 0 && closer.GetActiveTime().Add(p.maxLifetime).Before(time.Now()) {
            p.Close(closer)
            continue
        }
        return closer, nil
    }
}

func (p *GenericPool) getOrCreate() (Poolable, error) {
    select {
    case closer := <-p.pool:
        return closer, nil
    default:
    }
    p.Lock()
    if p.numOpen >= p.maxOpen {
        closer := <-p.pool
        p.Unlock()
        return closer, nil
    }
    // 新建连接
    closer, err := p.factory()
    if err != nil {
        p.Unlock()
        return nil, err
    }
    p.numOpen++
    p.Unlock()
    return closer, nil
}

// 释放单个资源到连接池
func (p *GenericPool) Release(closer Poolable) error {
    if p.closed {
        return ErrPoolClosed
    }
    p.pool <- closer
    return nil
}

// 关闭单个资源
func (p *GenericPool) Close(closer Poolable) error {
    p.Lock()
    closer.Close()
    p.numOpen--
    p.Unlock()
    return nil
}

// 关闭连接池,释放所有资源
func (p *GenericPool) Shutdown() error {
    if p.closed {
        return ErrPoolClosed
    }
    p.Lock()
    close(p.pool)
    for closer := range p.pool {
        closer.Close()
        p.numOpen--
    }
    p.closed = true
    p.Unlock()
    return nil
}
回帖
  • 消灭零回复
相关主题
golang利用通道chan实现一个通用的TCP连接池 0
golang实现内存池 go语言字节池byte pool实现代码 0
golang网络编程之基于TCP协议实现长连接 golang心跳检测 0
golang类型断言type的使用 0
golang依赖包管理 mod使用教程 0
golang表单验证库validator 0
如何使用Go语言实现一个简单的异步任务框架呢?生产者消费者模型 0
golang 网络编程设置keepAlive空闲多长时间开始探测、 探测总次数 0
golang网络编程之TCP编程详解 0
go语言利用ioutil.ReadAll读取TCP socket所有数据 0
golang利用io.copy和bytes.Buffer读取TCP socket所有数据 0
golang读取所有socket数据的方式一 0
围绕Handler接口的方法ServeHTTP,可以轻松的写出go中的中间件 0
golang语言错误处理方式check函数,把错误转化为panic 0
golang cannot find module providing package 0
golang语言错误处理errors包使用详解 0
golang基于通道channel实现一个通用连接池 pool 0
golang数据类型之map结构详解 0
golang socket关闭读导致 wsarecv: An existing connection was forcibly closed by the remote host 0
windows配置golang的环境变量 gopath配置学习go语言第一天 0
相关主题
QListWidget滚动的时候显示不完整 滚动条模式导致的哦 0
Qt设置顶层面板背景透明Qt::WA_TranslucentBackground 隐藏边框Qt::FramelessWindowHint 0
QMenu和QMenuBar样式表大全 qss 0
QT定时器startTimer和timerEvent事件 每隔interval 毫秒就会启动一次 0
C语言内存分配函数malloc和calloc的区别 0
QCheckBox的QSS样式表总结 0
Qt通过qRegisterMetaType注册自定义数据类型 0
QListwidget触发2次itemClicked事件 0
打印机USB驱动开发之实现打印服务器 0
Qt利用QLabel组件来显示图片 0
TableView自定义代理QStyledItemDelegate实现ComboBox 0
Qt利用QGraphicsView类实现图片放大缩小平移显示 0
Qt实现非阻塞延迟方法sleep 0
海康相机SDK的C++对应的接口 0
Qt实现webdav客户端功能支持https协议的webdav客户端 0
CHKDSK解决 移动硬盘只能看见盘符其它信息都看不见另外双击也打不开 0
gogs一直报errror:dial tcp xxx.xxx.xxx.xxx 宿主机的ip 0
索鸟快传2.1.2发布 0
索鸟快传2.1.1发布 0
Qt操作windows注册表的方法 bat从注册表中将键值删除 0