大家好,我是煎鱼。
之前 Go 核心团队乘机推广了标准库 v2 的更新计划,想着把一些老旧的标准库给干掉。在上一步已经把 math/rand/v2
给做了,官方也认为非常成功。
后续将开始计划新标准库 sync/v2
的更新和发布。今天文章主要分享此提案。
#go/issues/71076
本次新标准库 sync/v2
的一个改造重点,来自于一个一揽子提案《spec: add generic programming using type parameters[1]》:
图片
核心目的就是:“建议在 Go 语言中添加对类型参数的支持。这将改变 Go 语言,使其支持一种通用编程形式。”
简单来讲,就是用逐步用泛型重构一切适用的标准库。
当前 sync
包提供了 Map
和 Pool
类型。这些类型是在 Go 支持泛型之前设计的,其操作的值类型均为 any
。
图片
导致存在两个核心问题:
类型不安全:因设计于泛型前,依赖 any
类型,无法保证编译时类型校验,违背 Go 的类型安全原则;
性能损耗:非指针值转换为 any
需额外内存分配,导致存储字符串键或切片值时效率低下。
通过将 sync.Map
和 sync.Pool
改造为泛型类型,可以轻松解决和避免这些问题。
但是由于 Go1 兼容性保障。也就是兼容性问题,考虑 Go 核心团队无法直接修改现有 sync v1 包中的 Map 和 Pool 类型。
解决思路,将主要采取以下两点方法:
需要通过泛型重构为强类型设计。
新增 sync/v2
包引入泛型版本。例如:Map[K,V]/Pool[T]
,避免命名混乱(例如:PoolOf
的歧义),并支持 v1 到 v2 到平滑迁移。
这样后续大家可以直接通过类似 goimports
工具,直接将 v1 版本迁移至 sync/v2
,不需要手动处理导入路径。
同时可以保留原 v1 包兼容性,可以彻底解决后续类型与性能问题,符合 Go 语言长期演进方向和规范。
在 sync/v2
包中,以下类型和函数将与当前 sync
v1 包保持一致(不变):
func OnceFunc(f func()) func() func OnceValue[T any](f func( "T any") T) func() T func OnceValues[T1, T2 any](f func( "T1, T2 any") (T1, T2)) func() (T1, T2) type Cond struct{ ... } func NewCond(l Locker) *Cond type Locker interface{ ... } type Mutex struct{ ... } type Once struct{ ... } type RWMutex struct{ ... } type WaitGroup struct{ ... }
改造点之一:现有的 Map
类型将被需要两个类型参数的新泛型类型取代。需注意,原 Range
方法将变更为返回迭代器的 All
方法。
如下代码:
// Map is like a Go map[K]V but is safe for concurrent use // by multiple goroutines without additional locking or coordination. // ...and so forth type Map[K comparable, V any] struct { ... } // Load returns the value stored in the map for a key, or the zero value if no // value is present. // The ok result indicates whether value was found in the map. func (m *Map[K, V]) Load(key K) (value V, ok bool) // Store sets the value for a key. func (m *Map[K, V]) Store(key K, value V) // LoadOrStore returns the existing value for the key if present. // Otherwise, it stores and returns the given value. // The loaded result is true if the value was loaded, false if stored. func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) // LoadAndDelete deletes the value for a key, returning the previous value if any. // The loaded result reports whether the key was present. func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) // Delete deletes the value for a key. func (m *Map[K, V]) Delete(key K) // Swap swaps the value for a key and returns the previous value if any. // The loaded result reports whether the key was present. func (m *Map[K, V]) Swap(key K, value V) (previous V, loaded bool) // CompareAndDelete deletes the entry for key if its value is equal to old. // This panics if V is not a comparable type. // // If there is no current value for key in the map, CompareAndDelete // returns false. func (m *Map[K, V]) CompareAndDelete(key K, old V) (deleted bool) // CompareAndSwap swaps the old and new values for key // if the value stored in the map is equal to old. // This panics if V is not a comparable type. func (m *Map[K, V]) CompareAndSwap(key K, old, new V) (swapped bool) // Clear deletes all the entries, resulting in an empty Map. func (m *Map[K, V]) Clear() // All returns an iterator over the keys and values in the map. // ... and so forth func (m *Map[K, V]) All() iter.Seq2[K, V] // TODO: Consider Keys and Values methods that return iterators, like maps.Keys and maps.Values.
改造点之二:现有的 Pool 类型将被一个需要类型参数的新泛型类型取代。
同时新版 Pool 将不再暴露公开的 New
字段,改为通过 NewPool
函数创建实例。该函数接受一个生成新值的函数参数,用于替代原 New
字段的初始化行为。
如下代码:
// A Pool is a set of temporary objects of type T that may be individually saved and retrieved. // ...and so forth type Pool[T any] struct { ... } // NewPool returns a new pool. If the newf argument is not nil, then when the pool is empty, // newf is called to fetch a new value. This is useful when the values in the pool should be initialized. func NewPool[T any] (newf func() T) *Pool[T] // Put adds x to the pool. func (p *Pool[T]) Put(x T) // Get selects an arbitrary item from the Pool, removes it from the // Pool, and returns it to the caller. // ...and so forth // // If Get does not have a value to return, and p was created with a call to [NewPool] with a non-nil argument, // Get returns the result of calling the function passed to [NewPool]. func (p *Pool[T]) Get() T
之前标准库 math/rand/v2
在 Go 核心团队看来,已经取得了不错的成果。泛型在 Go1.18 也输出了(虽然还不完善),Go1 向前兼容性和向后兼容性保障的方式也确立了。
接下来想必 Go 就会每年更新几个 v2 标准库。这次 sync/v2
也是一次不错的改造,解决了不少原有的问题。大家可以小小期待一下。
[1]spec: add generic programming using type parameters: https://github.com/golang/go/issues/43651