goroutine池也可以是资源池

本文介绍如何使用无缓冲通道创建Goroutine池及资源池的管理方式,包括池的初始化、运行、关闭等过程。通过示例代码展示了Goroutine池与资源池的应用场景及其优势。

goroutine池:
● 使用无缓冲的通道创建一个goroutine池
● goroutine池中包含一组并发运行的任务,等待无缓冲通道内的任务握手,执行并发任务运行。
如代码:

package work

import "sync"

type Worker interface {
    // 任务接口
	Task()
}

// 池中包含Worker, 用无缓冲的通道初始化
type Pool struct {
	work chan Worker
	wg sync.WaitGroup
}

// 创新一个新的Pool, maxGoroutines定义并发工作的Worker的数量
// 同时可以控制速度
func New(maxGoroutines int) *Pool {
	p := Pool{
		// 无缓冲的
		work: make(chan Worker),
	}
	p.wg.Add(maxGoroutines)
	for i := 0;i < maxGoroutines; i++ {
		go func() {
			for w := range p.work {
				w.Task()
			}
			p.wg.Done()
		}()
	}
	return &p
}

// 开始工作的信号
func (p *Pool) Run(w Worker)  {
	p.work <- w
}

// 关闭池
func (p *Pool) ShutDown()  {
	close(p.work)
	p.wg.Wait()
}

测试程序:

package main

import (
	"log"
	"sync"
	"syncFiles/pool/work"
	"time"
)

// names 提供一组用来显示的名字
var names = []string{
	"steve",
	"bob",
	"mary",
	"therese",
	"jason",
}

type namePrinter struct {
	name string
}

// 实现Worker接口
func (nm *namePrinter) Task()  {
	log.Println(nm.name)
	time.Sleep(time.Second)
}

func main() {
	// 两个goroutine来创建工作池  并发量是2
	p := work.New(2)

	var wg sync.WaitGroup
	wg.Add(100*len(names))

	for i := 0; i <100; i++ {
		for _, name := range names {
			np := namePrinter{
				name: name,
			}
            // 500个并发请求 Worker池中的Worker来工作
            // Worker池中只开了两个并发通道,一个接一个的从通道内获取任务并执行
			go func() {
				p.Run(&np)
				wg.Done()
			}()
		}
	}
	wg.Wait()
	// 关闭线程池
	p.ShutDown()
}

如上这种模式,特别适合在请求蜂拥到了一块,Worker有限并可以控制的场景去使用。
PS:
比如老板某天说了,今天要对干点活,请求个接口刷点数据。速度要快。那么久可以多开几个Worker.
明天又让速度慢点,时间充足干完就行,设置一下参数就能轻松控制并发量和速度
另一种场景:
比较常用,就是资源池,比如数据库的连接池。当数据库连接有资源限制,又要保证比较高的速度和利用率的时候,就需要用到资源池了。

// 管理一组可以安全地在多个goroutine之间共享的资源
package pool

import (
	"errors"
	"io"
	"log"
	"sync"
)

// 被管理的资源需要实现  io.Closer的接口
type Pool struct {
	m	sync.Mutex
	resources	chan io.Closer
	factory	func()	(io.Closer, error)
	closed	bool
}

// ErrPoolClosed 表示请求了一个已经关闭的资源池
var ErrPoolClosed = errors.New("Pool has been closed")

func New(fn func() (io.Closer, error), size uint) (*Pool, error) {
	if size < 0 {
		return nil, errors.New("Size value too small.")
	}

	return &Pool{
		factory: fn,
		resources: make(chan io.Closer, size),
	}, nil
}

// 获取连接池资源
func (p *Pool) Acquire() (io.Closer, error) {
	select {
	case r, ok := <-p.resources:
		log.Println("Acquire: ", "Shared Resource")
		if !ok {
			return nil, ErrPoolClosed
		}
		return r, nil
	default:
		log.Println("Acquire:", "New Resource")
		return p.factory()
	}
}

// 对资源池的请求读写操作都需要是原子的,防止误操作
// 释放一个连接并将连接放回 资源池中
// 如果不能写入资源池或者资源池关闭 需要释放
func (p *Pool) Release(r io.Closer) {
	p.m.Lock()
	defer p.m.Unlock()
    
	if p.closed {
		r.Close()
		return
	}
    
	select {
	case p.resources <- r:
		log.Println("Release:", "In Queue")
	default:
		log.Println("Release", "Closing")
		r.Close()
	}
}

func (p *Pool) Close() {
	p.m.Lock()
	defer p.m.Unlock()

	if p.closed {
		return
	}

	p.closed = true
	close(p.resources)

	for r := range p.resources {
		r.Close()
	}
}

测试程序:

// pool包来共享一组模拟的数据库连接
package main

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

const (
	maxGoroutines = 25 // 要使用的goroutine的数量
	pooledResources = 10 // pool池中资源的数量
)

// 模拟的数据库连接
type dbConnection struct {
	ID int32
}

// 实现了 io.Closer接口 
func (dbConn *dbConnection) Close() error {
	log.Println("Close: Connection", dbConn.ID)
	return nil
}

// 记录每个连接的id
var idCounter int32

// 创建连接
func createConnection() (io.Closer, error) {
	id := atomic.AddInt32(&idCounter, 1)
	log.Println("Create: New Connection", id)

	return &dbConnection{id}, nil
}

func main() {
	var wg sync.WaitGroup
	wg.Add(maxGoroutines)

	// 创建用来管理连接的池
	p, err := pool.New(createConnection, pooledResources)
	if err != nil {
		log.Println(err)
	}

	for query :=0; query < maxGoroutines; query++ {
		// 模拟创建多个goroutine 从连接池中获取连接
		go func(q int) {
			performQueries(q, p)
			wg.Done()
		}(query)
	}

	wg.Wait()
	log.Println("Shutdown Program")
	p.Close()
}

func performQueries(query int, p *pool.Pool) {
	// 从池中获取一个连接
	conn, err := p.Acquire()
	if err != nil {
		log.Println(err)
		return
	}

	defer p.Release(conn)

    // 业务逻辑处理
	time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
	log.Printf("QID[%d] CID[%d]\n", query, conn.(*dbConnection).ID)
}

主程序可以自定义创建资源的逻辑,以及实现接口实现关闭的方法。
有点小疑问:

  • 创建多个goroutine去执行获取连接,Accquire的逻辑是如果资源通道没有则创建新的,那么实际上这个资源池还是个缓冲的作用,只能做到部分的连接重新重用,不能做到控制资源池资源总数的作用。
    可以复用在有资源限制的场景下。

总结:

  • 由此可见chan的灵活, 既可以将资源构建成Pool,不断请求资源形成高并发。
  • 也可以将工作者Worker—> Goroutine放在Pool中,作为任务执行单元,并发获取任务并执行。
  • 区别只在于前者用到可缓冲的通道chan, 后者用到无缓冲的通道chan 形成任务和发布者的握手。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值