• 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) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接着,通过一个简单的例子,来看看是如何使用的

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    实践应用

    在之前的文章中有提到的享元模式设计模式: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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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]) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 并发安全问题

    2.1 修改模型

    为了方便观察,给每个房间(棋牌室)增加一个创建时间

    type ChessBoard struct {
        Cards map[int]*ChessPiece
        Time  time.Time
    } 
    
    • 1
    • 2
    • 3
    • 4

    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()
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    输出如下:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可见,在多个goroutine的并发情况下,是安全,另外可以观察到,sync.Pool没有一直【Creating a new object】去New很多棋牌室。

    小结

    sync.Pool是Go语言标准库中的一个类型,它提供了对象的缓存池功能。它的主要用途是存储那些可以被复用的临时对象,以便在需要时快速获取,而不是每次都进行新的对象分配。且多个 goroutine 同时使用 Pool 是安全的。
    本文简述了sync.Pool的基础使用,以及了如何使用其对实践棋牌室游戏的案例进行优化过程。

    参考

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

  • 相关阅读:
    【Spring笔记02】Spring中的IOC容器和DI依赖注入介绍
    23、短信登录(基于redis实现共享session登录)
    Prometheus 与 VictoriaMetrics对比
    Java NIO详解
    mongodb如何多表查询,如同时查询店铺以及里面对应的商品
    计算机视觉 目标分割
    SOAP 和 REST 的区别
    自用 K8S 资源对象清单 YAML 配置模板手册-1
    dotnet 用 SourceGenerator 源代码生成技术实现中文编程语言
    一元多项式
  • 原文地址:https://blog.csdn.net/m0_73728511/article/details/134037283