Golang高并发Map实践:1000万次操作仅需14ms

夏知夏
夏知夏
发布时间:2026-10-13| 6人浏览过
原创

解决Go语言原生map在高并发场景下的线程安全问题及大内存压力,介绍如何配置支持泛型自定义类型的并发Map结构,并通过Upsert回调机制实现高效的原子计数器逻辑,最终验证多线程并行写入千万级数据的性能表现。

核心目标与准备条件

Go语言原生的map数据结构并非线程安全,且在数据量极大时若键值对为string等引用类型,会引发严重的垃圾回收(GC)压力。虽然存在如github.com/orcaman/concurrent-map等开源方案,但其键值往往硬编码为字符串类型。为了实现更高效的内存管理与业务适配,本例采用经过修改的go_concurrent_map(支持自定义泛型类型),重点演示如何利用其特性构建高性能的并发计数器。

基础使用流程

第1步:初始化并发Map实例

利用Go 1.18+引入的泛型特性,创建指定键值类型的并发Map。此处以uint64作为键,string作为值进行示范,相比字符串键可大幅降低内存开销。

// 创建一个新的 uint64->string 并发Map
m := cache.NewConcurrentMap[uint64, string]()
 
// Set:根据Key设置Value
m.Set(199010212, "bar")
 
// Get:获取指定Key对应的Value,ok表示是否存在
bar, ok := m.Get(199010212)
fmt.Println(bar, ok)
 
// Remove:移除指定Key及其关联数据
m.Remove(199010212)

第2步:实现原子更新逻辑(计数器案例)

若需执行累加器等“读取-修改-写入”的原子操作,直接加锁效率低下。本库提供Upsert方法配合回调函数来实现:当Key不存在时直接插入;存在时则调用回调函数对旧值和新值进行处理。以下是自定义结构体及应用回调的具体代码:

首先定义计数器结构体:
type BaseCounter struct { Count, CountLast uint64 }

var MapOfAppUserCount ConcurrentMap[uint64, *AppUserCounter]
 
func InitMaps() {
	MapOfAppVistedCount = NewConcurrentMap[uint64, *BaseCounter]()
}
 
// 回调函数:用于处理存在或新加的情况
func appAddCallBack(exist bool, valueInMap *BaseCounter, newValue *BaseCounter) *BaseCounter {
	if !exist {
		// Key不存在,直接返回传入的新值存入Map
		return newValue
	} else {
		// Key已存在,在现有值基础上累加
		valueInMap.Count += newValue.Count
		return valueInMap
	}
}
 
// 对外提供的加一接口
func AppAddBy(key uint64, i uint64) uint64 {
	c := BaseCounter{i, i}
	// Upsert自动判断存在性并执行回调
	res := MapOfAppVistedCount.Upsert(key, &c, appAddCallBack)
	if res != nil {
		return res.Count
	}
	return 0
}

第3步:业务集成调用

在主程序或服务启动阶段初始化地图,随后即可在业务逻辑中直接使用上述封装好的方法:

cache.InitMaps()
cache.AppAddBy(i, 1) // 模拟增加一次访问计数

实际性能测试与结果分析

通过对比单线程串行处理与多协程并行处理的耗时,验证该方案的并发能力。测试场景为累计1000万次计数器递增操作。

  • 单线程测试:在单个 Goroutine 中循环1000万次,耗时约为 2412 ms。受限于全局读写锁的串行排队,CPU利用率未能完全释放。
  • 多线程/协程测试:将任务拆分为100个子任务分配给不同的Goroutine并行写入,总耗时骤降至约 14 ms。这表明在合适的并发粒度下,底层Map的分片锁机制能够充分提升吞吐量。

完整的压测验证代码如下,展示了如何通过sync.WaitGroup控制并发数及计时节点:

func testSingle() {
	cache.InitMaps()
	timeUnixNano1 := time.Now().UnixMilli()
	// 1000万次更新
	for i := uint64(0); i < 10000000; i++ {
		cache.AppAddBy(i, 1)
	}
	timeUnixNano2 := time.Now().UnixMilli()
	delta := timeUnixNano2 - timeUnixNano1
	fmt.Println("cost: ", delta, " ms") // 预期 ~2412ms
 
	count := cache.AppAddBy(1, 1)
	fmt.Println(count)
	count = cache.AppAddBy(1, 2)
	fmt.Println(count)
	count = cache.AppAddBy(1, 3)
	fmt.Println(count)
}
 
var N int = 10000000
 
// 子协程插入逻辑
func doInsert(n int, index int, g *sync.WaitGroup) {
	m := N / n
	start := index * m
	for i := uint64(start); i < uint64(m)+uint64(start); i++ {
		cache.AppAddBy(i, 1)
	}
	if g != nil {
		g.Done()
	}
}
 
func testMulti() {
	cache.InitMaps()
 
	group := sync.WaitGroup{}
	n := 100 // 开启100个协程
	group.Add(n)
	timeUnixNano1 := time.Now().UnixMilli()
	for i := 0; i < n; i++ {
		go doInsert(n, i, &group)
	}
	group.Wait() // 等待所有写入完成
	timeUnixNano2 := time.Now().UnixMilli()
	delta := timeUnixNano2 - timeUnixNano1
	fmt.Println("cost: ", delta, " ms") // 预期 ~14ms
}

以上就是Golang利用并发Map实现高性能计数的详细内容,更多关于Golang并发优化与高并发Map实战的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。