Go语言之并发示例-Pool(一)

简介: 这篇文章演示使用有缓冲的通道实现一个资源池,这个资源池可以管理在任意多个goroutine之间共享的资源,比如网络连接、数据库连接等,我们在数据库操作的时候,比较常见的就是数据连接池,也可以基于我们实现的资源池来实现。

这篇文章演示使用有缓冲的通道实现一个资源池,这个资源池可以管理在任意多个goroutine之间共享的资源,比如网络连接、数据库连接等,我们在数据库操作的时候,比较常见的就是数据连接池,也可以基于我们实现的资源池来实现。


可以看出,资源池也是一种非常流畅性的模式,这种模式一般适用于在多个goroutine之间共享资源,每个goroutine可以从资源池里申请资源,使用完之后再放回资源池里,以便其他goroutine复用。


好了,老规矩,我们先构建一个资源池结构体,然后再赋予一些方法,这个资源池就可以帮助我们管理资源了。


//一个安全的资源池,被管理的资源必须都实现io.Close接口type Pool struct {
    m sync.Mutex
    res chan io.Closer
    factory func() (io.Closer,error)
    closed bool}
AI 代码解读


这个结构体Pool有四个字段,其中m是一个互斥锁,这主要是用来保证在多个goroutine访问资源时,池内的值是安全的。


res字段是一个有缓冲的通道,用来保存共享的资源,这个通道的大小,在初始化Pool的时候就指定的。注意这个通道的类型是io.Closer接口,所以实现了这个io.Closer接口的类型都可以作为资源,交给我们的资源池管理。


factory这个是一个函数类型,它的作用就是当需要一个新的资源时,可以通过这个函数创建,也就是说它是生成新资源的,至于如何生成、生成什么资源,是由使用者决定的,所以这也是这个资源池灵活的设计的地方。


closed字段表示资源池是否被关闭,如果被关闭的话,再访问是会有错误的。


现在先这个资源池我们已经定义好了,也知道了每个字段的含义,下面就开时具体使用。刚刚我们说到关闭错误,那么我们就先定义一个资源池已经关闭的错误。


var ErrPoolClosed = errors.New("资源池已经关闭。")


非常简洁,当我们从资源池获取资源的时候,如果该资源池已经关闭,那么就会返回这个错误。单独定义它的目的,是和其他错误有一个区分,这样需要的时候,我们就可以从众多的error类型里区分出来这个ErrPoolClosed


下面我们就该为创建Pool专门定一个函数了,这个函数就是工厂函数,我们命名为New


//创建一个资源池func New(fn func() (io.Closer, error), size uint) (*Pool, error) {
    if size <= 0 {
            return nil, errors.New("size的值太小了。")
    }   
    return &Pool{
        factory: fn,
        res:     make(chan io.Closer, size),
    }, nil}
AI 代码解读


这个函数创建一个资源池,它接收两个参数,一个fn是创建新资源的函数;还有一个size是指定资源池的大小。


这个函数里,做了size大小的判断,起码它不能小于或者等于 0 ,否则就会返回错误。如果参数正常,就会使用size创建一个有缓冲的通道,来保存资源,并且返回一个资源池的指针。


有了创建好的资源池,那么我们就可以从中获取资源了。


//从资源池里获取一个资源func (p *Pool) Acquire() (io.Closer,error) {
    select {
        case r,ok := <-p.res:
                log.Println("Acquire:共享资源")
                if !ok {
                            return nil,ErrPoolClosed
                }        
                return r,nil
        default:
                log.Println("Acquire:新生成资源")        
                return p.factory()
    }}
AI 代码解读


Acquire方法可以从资源池获取资源,如果没有资源,则调用factory方法生成一个并返回。


这里同样使用了select的多路复用,因为这个函数不能阻塞,可以获取到就获取,不能就生成一个。


这里的新知识是通道接收的多参返回,如果可以接收的话,第一参数是接收的值,第二个表示通道是否关闭。例子中如果ok值为false表示通道关闭,如果为true则表示通道正常。所以我们这里做了一个判断,如果通道关闭的话,返回通道关闭错误。


有获取资源的方法,必然还有对应的释放资源的方法,因为资源用完之后,要还给资源池,以便复用。在讲解释放资源的方法前,我们先看下关闭资源池的方法,因为释放资源的方法也会用到它。


关闭资源池,意味着整个资源池不能再被使用,然后关闭存放资源的通道,同时释放通道里的资源。


//关闭资源池,释放资源func (p *Pool) Close() {
    p.m.Lock()    
    defer p.m.Unlock()    
    if p.closed {        
          return
     }

    p.closed = true

    //关闭通道,不让写入了
    close(p.res)    //关闭通道里的资源
    for r:=range p.res {
        r.Close()
    }}
AI 代码解读


这个方法里,我们使用了互斥锁,因为有个标记资源池是否关闭的字段closed需要再多个goroutine操作,所以我们必须保证这个字段的同步。这里把关闭标志置为true


然后我们关闭通道,不让写入了,而且我们前面的Acquire也可以感知到通道已经关闭了。同比通道后,就开始释放通道中的资源,因为所有资源都实现了io.Closer接口,所以我们直接调用Close方法释放资源即可。


关闭方法有了,我们看看释放资源的方法如何实现。


func (p *Pool) Release(r io.Closer){   
     //保证该操作和Close方法的操作是安全的
    p.m.Lock()    
    defer p.m.Unlock() 
    //资源池都关闭了,就省这一个没有释放的资源了,释放即可
    if p.closed {
        r.Close()        
        return
    }
    select {
    case p.res <- r:
        log.Println("资源释放到池子里了")    
    default:
        log.Println("资源池满了,释放这个资源吧")
        r.Close()
    }}
AI 代码解读


释放资源本质上就会把资源再发送到缓冲通道中,就是这么简单,不过为了更安全的实现这个方法,我们使用了互斥锁,保证closed标志的安全,而且这个互斥锁还有一个好处,就是不会往一个已经关闭的通道发送资源。


这是为什么呢?因为Close和Release这两个方法是互斥的,Close方法里对closed标志的修改,Release方法可以感知到,所以就直接return了,不会执行下面的select代码了,也就不会往一个已经关闭的通道里发送资源了。


如果资源池没有被关闭,则继续尝试往资源通道发送资源,如果可以发送,就等于资源又回到资源池里了;如果发送不了,说明资源池满了,该资源就无法重新回到资源池里,那么我们就把这个需要释放的资源关闭,抛弃了。


目录
相关文章
Go语言grequests库并发请求的实战案例
Go语言grequests库并发请求的实战案例
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
Go语言中的map为什么默认不是并发安全的?
Go语言的map默认不保证并发安全,以优化性能和简洁性。官方建议在需要时使用`sync.Mutex`保证安全。从Go 1.6起,并发读写map会导致程序崩溃,鼓励开发者显式处理并发问题。这样做的哲学是让代码更清晰,并避免不必要的性能开销。
70 0
Go语言map并发安全使用的正确姿势
在Go并发编程中,由于普通map不是线程安全的,多goroutine访问可能导致数据竞态。为保证安全,可使用`sync.Mutex`封装map或使用从Go 1.9开始提供的`sync.Map`。前者通过加锁手动同步,后者内置并发控制,适用于多goroutine共享。选择哪种取决于具体场景和性能需求。
79 0
|
8月前
|
Golang深入浅出之-Go语言中的并发安全容器:sync.Map与sync.Pool
Go语言中的`sync.Map`和`sync.Pool`是并发安全的容器。`sync.Map`提供并发安全的键值对存储,适合快速读取和少写入的情况。注意不要直接遍历Map,应使用`Range`方法。`sync.Pool`是对象池,用于缓存可重用对象,减少内存分配。使用时需注意对象生命周期管理和容量控制。在多goroutine环境下,这两个容器能提高性能和稳定性,但需根据场景谨慎使用,避免不当操作导致的问题。
234 7
Golang深入浅出之-Go语言中的并发模式:Pipeline、Worker Pool等
【5月更文挑战第1天】Go语言并发模拟能力强大,Pipeline和Worker Pool是常用设计模式。Pipeline通过多阶段处理实现高效并行,常见问题包括数据竞争和死锁,可借助通道和`select`避免。Worker Pool控制并发数,防止资源消耗,需注意任务分配不均和goroutine泄露,使用缓冲通道和`sync.WaitGroup`解决。理解和实践这些模式是提升Go并发性能的关键。
99 2
Go语言map这么查询 才是高手风范
Go语言map这么查询 才是高手风范
308 0
GO语言-10了解Go并发中锁的概念和使用(下)
GO语言的学习与记录,10了解Go并发中锁的概念和使用(下),学习锁的概念,为什么需要锁;使用互斥锁解决临界区资源的问题
109 0
Go语言,sync包如何控制并发?
除了 channel 通道,还有 sync.Mutex、sync.WaitGroup 这些原始的同步机制来,更加灵活的实现数据同步和控制并发。
96 0
Go语言,sync包如何控制并发?
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等