Golang并发安全泛型集合(Set)设计与实现
并发安全泛型集合core/pkg/set设计与实现1. 简介set包提供CSet[T comparable]在 Go 泛型下实现的线程安全集合元素不重复。底层使用map[T]struct{}作为紧凑存储空 struct 不占值内存对外通过sync.RWMutex隔离并发读写。VSS项目应用流名去重、会话 key 占用标记、邀请/拉流防并发击穿、设备侧状态去重等的场景。2. 设计要点2.1 为何不导出底层 map 类型底层setMap[T]不导出仅CSet[T]暴露 API避免绕过锁直接改map的误用。2.2Range快照遍历与死锁规避若在RWMutex读锁持有期间执行用户回调f而f内部再次调用同一CSet的Add/Remove写锁会典型地读锁未释放又等写锁在标准RWMutex上容易死锁。因此Range实现为短临界区在锁内仅复制当前键列表snapshotKeys锁外枚举对快照逐元素调用f。2.3Values与快照Values()与snapshotKeys一致返回当前时刻所有元素的一份新切片顺序未定义与map遍历顺序一致。适合一次性打印或调试不适合依赖稳定排序若需要排序请调用方slices.Sort。2.4 容量提示New(hint uint)New将hint传给make(map, hint)仅为减少扩容次数不是集合大小上限。高并发写热点集合时可适当增大 hint。2.5nil接收者对(*CSet[T])(nil)调用Add/Remove/Clear/Range等为空操作不 panicContains为 falseIsEmpty为 trueSize为 0Values为 nil与常见「防御性」用法一致。2.6 批量Add/RemoveAdd(elements ...T)多参数等价于依次加入单次持锁完成减少锁次数。Remove(elements ...T)批量删除。3. API 一览方法说明New[T](hint uint) *CSet[T]初始化构造Add(elements ...T)并入集合Remove(elements ...T)删除Clear()清空Contains(T) bool是否包含IsEmpty() bool是否为空Size() int元素个数Range(func(T) bool)快照遍历false提前结束Values() []T快照切片4. 在项目中的典型用途VSSServiceContext中示例PubStreamExistsState记录已存在推流名避免重复占流InviteRequestState同一streamName防击穿FetchDeviceVideoState/TalkSipSendStatus设备或会话的进行中标记。这类场景共同特点高并发读Contains、短路径写Add/RemoveRWMutex较互斥锁更友好。5. 与其它容器选型需求更适合键值对、按 key 取 valuexmap/sync.Map/ 业务 map锁只关心成员与否、去重CSet读多写少、key 为 string可考虑sync.Map6. 并发与性能提示读多写少时Contains/Size/IsEmpty走读锁可并行。Range/Values会与写操作抢占锁复制快照快照长度大时复制成本与O(n)内存分配相关避免在超大集合上高频调用。基准测试go test ./core/pkg/set/... -bench. -benchmem7. 总结core/pkg/set用小 API 面 不导出底层 map 快照式Range在保持并发安全的同时规避绕过锁与Range回调重入死锁两类常见问题配合New与批量 Add/Remove适合在信令与媒体网关中做轻量级集合状态。