在 https://golang.org/cl/101715 的审查中(“regexp:使用sync.Pool缓存regexp.machine对象”),@ianlancetaylor 指出:
在 https://golang.org/cl/44150043 中,[@bradfitz] 做了类似的更改。但在 https://golang.org/cl/48190043 中将其回滚。[@dvyukov] 说:“剩下的担忧是:是否存在程序创建了许多本地短暂的正则表达式(一次性)的情况,而无法用全局的替换它们?对于这种情况,我们引入了一个全局的争用互斥锁。”
我猜这里的评论指的是 allPoolsMu
:
go/src/sync/pool.go
2e84dc2 的第 242 行
| | allPoolsMuMutex |
我觉得 sync.Pool
本不应该有全局的 Mutex
。毕竟,sync.Pool
明确与垃圾回收器合作(在 #22950 之后可能会更加合作),而垃圾回收器会在正常垃圾回收过程中追踪所有活跃的 sync.Pool
示例。allPools
切片似乎存在,以便垃圾回收器可以在追踪它们之前清除所有的 sync.Pool
示例。我们需要在追踪它们之前识别出 Pool
,以免过度保留池化对象,但这似乎可以在通常的 GC 安全点之外而不获取全局锁的情况下实现。
例如,我们可以将新分配的池添加到每个G的列表中,并仅在该运行 G 的线程获得调度器锁时将该列表移动到全局列表。
如果我们碰巧错过了当前 GC 周期上的新 Pool
(例如,因为其 goroutine 直到循环的末尾才被取消调度),那没关系:我们只需等待下一个 GC 周期来清除它。
这可以使 sync.Pool
对于倾向于长期存在但有时也短暂存在的对象(如编译后的正则表达式)更高效。
3条答案
按热度按时间umuewwlo1#
(另请参阅#24411,激励性示例。)
ktecyv1j2#
例如,我们可以将新分配的池添加到每个G列表中。
今天我尝试解决这个问题。
我的猜测是每个G列表是一个P本地存储,因此有两种实现方法。
方案1是修改运行时以支持,方案2是通过链接名从运行时获取返回pid的函数,并使用此函数创建P本地存储。
我尝试了方案2,如果你使用map来实现P本地存储,你仍然需要一个锁来同步。你也可以尝试使用切片来实现P本地存储。
最后发现也许可以使用类似于https://go-review.googlesource.com/c/go/+/552515 API的方法,使用两个分片来解决问题。
注意:如果GOMAXPOCS发生变化,分片并不能保证相同的P操作使用相同的内存,不同的Ps操作使用不同的内存。这意味着使用分片只是使锁不再是全局的,使得对相同锁的竞争不再激烈。
这些是我的尝试。
我分享它是为了帮助解决这个问题。
k97glaaz3#
https://go.dev/cl/562336提到了这个问题:
sync: eliminate global Mutex in (*Pool).pinSlow operations