主页 > 互联网  > 

Go并发:使用sync.Pool来性能优化

Go并发:使用sync.Pool来性能优化
简介

在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的基础使用,以及了如何使用其对实践棋牌室游戏的案例进行优化过程。

参考

官方doc 设计模式:flyweight(享元

标签:

Go并发:使用sync.Pool来性能优化由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Go并发:使用sync.Pool来性能优化

上一篇
274.H指数

下一篇
重入漏洞EtherStore