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

简介

在Go提供如何实现对象的缓存池功能?常用一种实现方式是:sync.Pool, 其旨在缓存已分配但未使用的项目以供以后重用,从而减轻垃圾收集器(GC)的压力。

快速使用

sync.Pool的结构也比较简单,常用的方法有Get、Put

go 复制代码
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) 

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

go 复制代码
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一个对象的问题,但还存在几个优化点:

  1. 不能只能缓存特定的棋牌室类型对象;
  2. 并发安全问题

原来是通过Factory工厂+Map实现享元模式,截取其中部分代码如下

go 复制代码
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的实现:

go 复制代码
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 修改模型

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

go 复制代码
type ChessBoard struct {
    Cards map[int]*ChessPiece
    Time  time.Time
} 
2.2 并发测试

启动多个goroutine进行测试

go 复制代码
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()
} 

输出如下:

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

参考

相关推荐
吴佳浩21 小时前
Go史上最大“打脸”现场来了:泛型方法终于实现了
后端·go
明月_清风1 天前
深入 Go 并发编程:从 Goroutine 到 Channel 的系统性避坑指南
后端·go
用户34232323763172 天前
开源!Go+Wails+Vue3 手搓一个 PLC 实时监控桌面工具
go
止语Lab2 天前
为什么你的 Go TCP server P99 延迟这么高
go
Andy Dennis2 天前
nsq学习记录
消息队列·go·nsq
韦胖漫谈IT2 天前
选语言不是站队,是选适合问题的工具
java·python·ai·rust·go·技术落地
喵个咪3 天前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
夜悊3 天前
Go网络编程的学习代码示例:客户端/服务端(C/S)模型
go
审判长烧鸡3 天前
【AI问答】GO代码循环返值
go
捧 花3 天前
Eino框架记忆功能实现指南
go·agent·eino