探索高性能并发安全映射:Skipmap
在Go语言的世界里,高效并发处理一直是开发者追求的目标。今天,我们将介绍一个令人兴奋的开源项目——Skipmap,这是一个基于跳表(skip-list)的高性能、可扩展、并发安全的映射库。如果你正在寻找一种能够在多线程环境下表现出色的数据结构,那么Skipmap绝对值得你一试。
项目介绍
Skipmap是一个基于跳表的高性能并发安全映射库。它通过实现A Simple Optimistic Skiplist Algorithm,在典型的操作模式(100000次操作,90%LOAD、9%STORE、1%DELETE,8核16线程)下,Skipmap的性能比Go内置的sync.Map快达10倍。
项目技术分析
Skipmap的核心优势在于其并发安全和高效的性能。与sync.Map不同,Skipmap中的键始终保持排序状态,并且Load
和Range
操作是等待自由的(wait-free),这意味着只要goroutine持续执行步骤,无论其他goroutine的活动如何,它都能保证完成操作。
项目及技术应用场景
Skipmap特别适用于以下场景:
- 需要排序键的场景:如果你需要一个键始终保持排序状态的映射,Skipmap是理想的选择。
- 并发调用多个操作的场景:例如同时使用
Range
和Store
操作,Skipmap能显著提升性能。
如果主要由单个goroutine访问映射,进行批量插入后仅使用Load
或Range
操作,使用内置的map可能更为合适。
项目特点
- 可扩展、高性能、并发安全:Skipmap在多线程环境下表现出色,性能优于sync.Map。
- 等待自由的
Load
和Range
操作:提供比锁自由(lock-free)更强的保证。 - 键值对始终排序:适用于需要键排序的场景。
快速开始
你可以通过以下代码快速开始使用Skipmap:
package main
import (
"fmt"
"strconv"
"github.com/zhangyunhao116/skipmap"
)
func main() {
// 类型化键和泛型值
m0 := skipmap.NewString[int]()
for _, v := range []int{10, 12, 15} {
m0.Store(strconv.Itoa(v), v+100)
}
v, ok := m0.Load("10")
if ok {
fmt.Println("skipmap load 10 with value ", v)
}
m0.Range(func(key string, value int) bool {
fmt.Println("skipmap range found ", key, value)
return true
})
m0.Delete("15")
fmt.Printf("skipmap contains %d items\r\n", m0.Len())
// 泛型键和值
m1 := skipmap.New[string, int]()
for _, v := range []int{11, 13, 16} {
m1.Store(strconv.Itoa(v), v+100)
}
m1.Range(func(key string, value int) bool {
println("m1 found ", key, value)
return true
})
// 泛型键和值带比较函数
m2 := skipmap.NewFunc[int, string](func(a, b int) bool { return a < b })
for _, v := range []int{15, 17, 19} {
m2.Store(v, strconv.Itoa(v+200))
}
m2.Range(func(key int, value string) bool {
println("m2 found ", key, value)
return true
})
}
注意:泛型API的性能通常低于类型化API,但在某些场景(如函数式编程)中更为适用。
性能基准
Skipmap在性能基准测试中表现优异,特别是在高并发场景下,其性能远超sync.Map。详细的性能数据和基准测试结果可以在项目的GitHub页面找到。
总之,如果你在寻找一个高性能、并发安全的映