V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
afxcn
V2EX  ›  Go 编程语言

请教, sync.Pool 怎么正确缓存一个列表?

  •  
  •   afxcn ·
    afxcn · 190 天前 · 1914 次点击
    这是一个创建于 190 天前的主题,其中的信息可能已经有所发展或是发生改变。

    相关代码如下,我能确保的是,Create 和 Release 是成对出现的;通过下面代码第一次拿到的数据是对的,多次就会出现混乱了。

    func (r *ApplicationRepository) GetApplications(filter string, orderBy string, page int, pageSize int) (*model.ApplicationCollection, error) {
    
    	var sqlx strings.Builder
    	var args []any
    
    	sqlx.WriteString("SELECT `id`, `application_name`, `created_at`, `updated_at` ")
    	sqlx.WriteString("FROM `applications` ")
    	sqlx.WriteString("WHERE `status` >= 0 ")
    
    	if filter != "" {
    		sqlx.WriteString("AND ")
    		if err := utils.SqlFilter(filter, &sqlx, &args, "", r.tryParse); err != nil {
    			return nil, err
    		}
    		sqlx.WriteString(" ")
    	}
    
    	if orderBy != "" {
    		sqlx.WriteString("ORDER BY ")
    		if err := utils.SqlOrderBy(orderBy, &sqlx, "", r.tryParseKey); err != nil {
    			return nil, err
    		}
    		sqlx.WriteString(" ")
    	}
    
    	sqlx.WriteString("limit ? offset ?")
    
    	if pageSize > _maxPageSize {
    		pageSize = _maxPageSize
    	} else if pageSize <= 0 {
    		pageSize = _pageSize
    	}
    
    	offset := 0
    
    	if page > 1 {
    		offset = (page - 1) * pageSize
    	}
    
    	args = append(args, pageSize, offset)
    
    	rows, err := query(sqlx.String(), args...)
    
    	if err != nil {
    		return nil, err
    	}
    
    	defer rows.Close()
    
    	applications := model.CreateApplicationCollection()
    
    	for rows.Next() {
    
    		application := model.CreateApplication()
    
    		err := rows.Scan(&application.ID, &application.ApplicationName, &application.CreatedAt, &application.UpdatedAt)
    
    		if err != nil {
    			return nil, err
    		}
    
    		*applications = append(*applications, *application)
    	}
    
    	return applications, rows.Err()
    }
    
    package model
    
    import (
    	"time"
    )
    
    // Application model
    // @Entity tableName="applications"
    type Application struct {
    	// @PrimaryKey
    	ID uint64 `json:"id"`
    	ApplicationName string `json:"applicationName"`
    	CreatedAt *time.Time `json:"createdAt"`
    	UpdatedAt *time.Time `json:"updatedAt"`
    }
    
    // ApplicationCollection Application list
    type ApplicationCollection []Application
    
    package model
    
    import "sync"
    
    var (
    	_applicationPool = sync.Pool{
    		New: func() any {
    			application := &Application{}
    			return application
    		}}
    
    	_applicationsPool = sync.Pool{
    		New: func() any {
    			applications := &ApplicationCollection{}
    			return applications
    		}}
    )
    
    // CreateApplication return *Application
    func CreateApplication() *Application {
    
    	application := _applicationPool.Get().(*Application)
    
    	return application
    }
    
    func (o *Application) initial() {
    	o.ID = 0
    	o.ApplicationName = ""
    	o.CreatedAt = nil
    	o.UpdatedAt = nil
    }
    
    func (o *Application) Release() {
    	o.initial()
    	_applicationPool.Put(o)
    }
    
    // CreateApplicationCollection return *ApplicationCollection
    func CreateApplicationCollection() *ApplicationCollection {
    
    	applicationCollection := _applicationsPool.Get().(*ApplicationCollection)
    
    	return applicationCollection
    }
    
    func (o *ApplicationCollection) initial() {
    	*o = (*o)[0:0]
    }
    
    func (o *ApplicationCollection) Release() {
    	for i := 0; i < o.Len(); i++ {
    		(*o)[i].Release()
    	}
    	o.initial()
    	_applicationsPool.Put(o)
    }
    
    第 1 条附言  ·  190 天前

    我们目前暂时的解决办法是去掉_applicationsPool,不再缓存列表对象。

    代码如下:

    package model
    
    import "sync"
    
    var (
    	_applicationPool = sync.Pool{
    		New: func() any {
    			application := &Application{}
    			return application
    		}}
    )
    
    // CreateApplication return *Application
    func CreateApplication() *Application {
    
    	application := _applicationPool.Get().(*Application)
    
    	return application
    }
    
    func (o *Application) initial() {
    	o.ID = 0
    	o.ApplicationName = ""
    	o.CreatedAt = nil
    	o.UpdatedAt = nil
    }
    
    func (o *Application) Release() {
    	o.initial()
    	_applicationPool.Put(o)
    }
    
    // CreateApplicationCollection return *ApplicationCollection
    func CreateApplicationCollection() *ApplicationCollection {
    
    	applications := &ApplicationCollection{}
    
    	return applications
    }
    
    func (o *ApplicationCollection) Release() {
    	for i := 0; i < o.Len(); i++ {
    		(*o)[i].Release()
    	}
    }
    
    第 2 条附言  ·  170 天前

    append 的 问题,换成make就没问题了。

    错误的写法:

    applications := &ApplicationCollection{}

    *applications = append(*applications, *application)

    正确的做法:

    applications := make(ApplicationCollection, 0, 10)

    applications[i] = application

    不过在这种场景下使用sync.Pool带来的好处比不上它带来的麻烦,除非是访问并发很高,并且cpu和内存很贵的环境才有必要。

    19 条回复    2024-05-30 17:02:17 +08:00
    MoYi123
        1
    MoYi123  
       190 天前   ❤️ 2
    建议把代码里无关的东西去掉, 然后搞一个带 main 函数的, 直接能运行的 demo.
    Nazz
        2
    Nazz  
       190 天前
    你把内存池当缓存用了?
    afxcn
        3
    afxcn  
    OP
       190 天前
    我自己怀疑问题可能出在:

    *applications = append(*applications, *application)



    *o = (*o)[0:0]

    这些代码里。
    afxcn
        4
    afxcn  
    OP
       190 天前
    @Nazz 缓存的是对象,不是数据,主要是在并发高的时候可以少占点内存吧。
    voidmnwzp
        5
    voidmnwzp  
       190 天前 via iPhone
    sync.pool 是为了减少在堆上分配内存以缓解 gc 的压力,一般我都是在频繁使用的对象或者比较大的对象才会考虑用 pool
    zzhaolei
        6
    zzhaolei  
       190 天前
    挺好奇为什么 _applicationPool 要用下划线开头?
    dyllen
        7
    dyllen  
       190 天前
    没怎么看明白,说的混乱是什么意思。
    afxcn
        8
    afxcn  
    OP
       190 天前
    @dyllen 返回的数据是错误的,就是有些数据丢失,有些重复了。

    @zzhaolei 个人习惯,只要是全局变量都加个下划线 。
    nagisaushio
        9
    nagisaushio  
       190 天前
    你要保证放回 pool 时对象没有其他引用
    afxcn
        10
    afxcn  
    OP
       190 天前
    @nagisaushio 也就是说对象不能自己 Release 自己,因为自己还指向自己?

    如果是这样的话,我的整个写法都是错误的了。得好好验证。
    zzhaolei
        11
    zzhaolei  
       190 天前
    @afxcn 不导出的加一个下划线还行,能区分,导出的全局变量你加吗?不加就不统一,加了就不能导出
    Orlion
        12
    Orlion  
       190 天前
    确定 ApplicationCollection.Release()调用时机是正确的吗? Release()之后是否还使用到了 ApplicationCollection 或者里面的元素?建议将 Release()的调用代码也发出来。
    afxcn
        13
    afxcn  
    OP
       190 天前
    @Orlion 用的是这个库: https://github.com/gostartkit/web/blob/master/application.go

    主要代码是这个 #218 - #221 行

    ```go
    if rel, ok := val.(IRelease); ok {
    rel.Release()
    val = nil
    }
    ```
    kfish
        14
    kfish  
       190 天前
    @zzhaolei 写其他语言带过来的风格吧, 不过 Go 里面还是少见下划线开头的代码
    cannotagreemore
        15
    cannotagreemore  
       189 天前
    你的 _applicationsPool 这里 New 这个 slice 的地方可以贴一下吗,感觉大概率是这里有点问题
    afxcn
        16
    afxcn  
    OP
       189 天前
    @cannotagreemore 您指的是这个:type ApplicationCollection []Application ?

    还是这个:

    _applicationsPool = sync.Pool{
    New: func() any {
    applications := &ApplicationCollection{}
    return applications
    }}
    cannotagreemore
        17
    cannotagreemore  
       187 天前
    这里 new 的时候显式指定一个最大的 Capacity 试一下
    ```
    _applicationsPool = sync.Pool{
    New: func() any {
    applications := &ApplicationCollection{}
    return applications
    }}
    ```
    afxcn
        18
    afxcn  
    OP
       170 天前
    @cannotagreemore 确实是 Capacity 的问题。
    cannotagreemore
        19
    cannotagreemore  
       169 天前
    @afxcn 这里是因为你前面的写法没有显示指定最大的 capacity ,默认的 cap=0 ,在 append 操作的时候会触发扩容。其实只需要使用 applications := make(ApplicationCollection, 0, 10) 来初始化就可以了,依然可以使用 append 去进行操作。
    感觉这个场景确实没什么必要针对 slice 使用 sync.Pool 就是了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2608 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 04:56 · PVG 12:56 · LAX 20:56 · JFK 23:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.