golang并发之使用sync.Pool优化性能
作者:jefffff
简介
在Go提供如何实现对象的缓存池功能?常用一种实现方式是:sync.Pool, 其旨在缓存已分配但未使用的项目以供以后重用,从而减轻垃圾收集器(GC)的压力。
快速使用
sync.Pool的结构也比较简单,常用的方法有Get、Put
type Pool struct { local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal localSize uintptr // size of the local array victim unsafe.Pointer // local from previous cycle victimSize uintptr // size of victims array // New optionally specifies a function to generate // a value when Get would otherwise return nil. // It may not be changed concurrently with calls to Get. New func() any } func (p *Pool) Get() any func (p *Pool) Put(x any)
接着,通过一个简单的例子,来看看是如何使用的
package main import ( "fmt" "sync" ) type Object struct { ID int // ... } func main() { // 1.创建一个sync.Pool对象 pool := &sync.Pool{ New: func() interface{} { fmt.Println("Creating a new object") return &Object{} }, } // 2.pool.Get()方法从池中获取一个对象。如果池中有可用的对象,Get()方法将返回其中一个;否则,它将返回一个新创建的对象 obj := pool.Get().(*Object) // 3.操作对象 obj.ID = 1 // 4.调用pool.Put()方法将对象放回池中 pool.Put(obj) objBar := pool.Get().(*Object) fmt.Println("Object ID:", objBar.ID) }
实践应用
在之前的文章中有提到的享元模式设计模式:flyweight(享元)的在棋牌游戏的应用的案例。今天我们使用sync.Pool对该方案进行优化。 观察在棋牌游戏的代码,虽然解决了每次都要New一个对象的问题,但还存在几个优化点:
- 不能只能缓存特定的棋牌室类型对象;
- 并发安全问题
原来是通过Factory工厂+Map实现享元模式,截取其中部分代码如下
package design_mode import "fmt" var chessPieceUnit = map[int]*ChessPiece{ 1: { Name: "車", Color: "紅", PositionX: 1, PositionY: 11, }, 2: { Name: "馬", Color: "黑", PositionX: 2, PositionY: 2, }, // 其他棋子 } func NewChessPieceUnitFactory() *ChessBoard { board := &ChessBoard{Cards: map[int]*ChessPiece{}} for id := range chessPieceUnit { board.Cards[id] = chessPieceUnit[id] } return board }
1.重构Factory
接着,我们同sync.Pool修改一下Factory的实现:
pool := &sync.Pool{ New: func() interface{} { fmt.Println("Creating a new object") return NewChessBoard() }, } game1 := pool.Get().(*ChessBoard) game2 := pool.Get().(*ChessBoard) fmt.Println(game1) fmt.Println(game2) fmt.Println(game1.Cards[0] == game2.Cards[0])
2. 并发安全问题
2.1 修改模型
为了方便观察,给每个房间(棋牌室)增加一个创建时间
type ChessBoard struct { Cards map[int]*ChessPiece Time time.Time }
2.2 并发测试
启动多个goroutine进行测试
func main() { pool := &sync.Pool{ New: func() interface{} { fmt.Println("Creating a new object") return NewChessBoard() }, } var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() obj := pool.Get().(*ChessBoard) obj.Time = time.Now() pool.Put(obj) fmt.Printf("Object ID: %v\n", obj.Time) }(i) } wg.Wait() }
输出如下:
Creating a new object
Creating a new object
Object ID: 2023-10-22 15:41:50.309343 +0800 CST m=+0.003511901
Object ID: 2023-10-22 15:41:50.3117423 +0800 CST m=+0.005911201
Object ID: 2023-10-22 15:41:50.3117423 +0800 CST m=+0.005911201
Object ID: 2023-10-22 15:41:50.3117423 +0800 CST m=+0.005911201
Object ID: 2023-10-22 15:41:50.3117423 +0800 CST m=+0.005911201
Object ID: 2023-10-22 15:41:50.3117423 +0800 CST m=+0.005911201
Object ID: 2023-10-22 15:41:50.3117423 +0800 CST m=+0.005911201
Object ID: 2023-10-22 15:41:50.3117423 +0800 CST m=+0.005911201
Object ID: 2023-10-22 15:41:50.3117423 +0800 CST m=+0.005911201
Object ID: 2023-10-22 15:41:50.3117423 +0800 CST m=+0.005911201
可见,在多个goroutine的并发情况下,是安全,另外可以观察到,sync.Pool没有一直【Creating a new object】去New很多棋牌室。
小结
sync.Pool是Go语言标准库中的一个类型,它提供了对象的缓存池功能。它的主要用途是存储那些可以被复用的临时对象,以便在需要时快速获取,而不是每次都进行新的对象分配。且多个 goroutine 同时使用 Pool 是安全的。
本文简述了sync.Pool的基础使用,以及了如何使用其对实践棋牌室游戏的案例进行优化过程。
以上就是golang并发之使用sync.Pool优化性能的详细内容,更多关于go sync.Pool的资料请关注脚本之家其它相关文章!