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 形成任务和发布者的握手。
本文介绍如何使用无缓冲通道创建Goroutine池及资源池的管理方式,包括池的初始化、运行、关闭等过程。通过示例代码展示了Goroutine池与资源池的应用场景及其优势。
3056

被折叠的 条评论
为什么被折叠?



