golang使用map支持高并发的方法(1000万次操作14ms)

语言原生的map存在2个问题:

1)不是线程安全的;

2)数据量大时候需要尽量避免使用string等,GC压力很大;

有人使用泛型实现了相关的cocurent-map,(https://github.com/orcaman/concurrent-map)但是关于键值部分仍然默认使用了string,为了提高效率,这里对其做了一些修改,让键值也可以自定义类型:https://github.com/robinfoxnan/go_concurrent_map

基本使用方法:

	// Create a new map.
	m := cache.NewConcurrentMap[uint64, string]()
 
	// Sets item within map, sets "bar" under key "foo"
	m.Set(199010212, "bar")
 
	// Retrieve item from map.
	bar, ok := m.Get(199010212)
	fmt.Println(bar, ok)
 
	// Removes item under key "foo"
	m.Remove(199010212)

为了实现计数器等,需要在加锁期间更新,需要使用回调函数:

// 计数器
type BaseCounter struct {
	Count     uint64
	CountLast uint64
}
 
var MapOfAppUserCount ConcurrentMap[uint64, *AppUserCounter]
 
func InitMaps() {
	MapOfAppVistedCount = NewConcurrentMap[uint64, *BaseCounter]()
}
 
// 没有值,则设置;如果有,则更新; 新增的部分通过新的值传递过来!
func appAddCallBack(exist bool, valueInMap *BaseCounter, newValue *BaseCounter) *BaseCounter {
	if exist == false {
		return newValue
	} else {
		valueInMap.Count += newValue.Count
		return valueInMap
	}
}
 
// 对应用计数器加i
func AppAddBy(key uint64, i uint64) uint64 {
	c := BaseCounter{i, i}
	res := MapOfAppVistedCount.Upsert(key, &c, appAddCallBack)
	if res != nil {
		return res.Count
	}
	return 0
}

计数器的使用如下:

cache.InitMaps()
cache.AppAddBy(i, 1)

性能:

1)单线程初始化1~1000w的计数器,2412  ms

2)分给100个协程,14ms

测试代码如下:

 func testSingle() {
	cache.InitMaps()
	timeUnixNano1 := time.Now().UnixMilli()
	// 100万次更新
	for i := uint64(0); i < 10000000; i++ {
		cache.AppAddBy(i, 1)
	}
	timeUnixNano2 := time.Now().UnixMilli()
	delta := timeUnixNano2 - timeUnixNano1
	fmt.Println("cost: ", delta, " ms")
 
	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
 
	//fmt.Println("thread ", index, "from ", start)
	for i := uint64(start); i < uint64(m); i++ {
		cache.AppAddBy(i, 1)
	}
	if g != nil {
		g.Done()
	}
}
 
func testMulti() {
	cache.InitMaps()
 
	group := sync.WaitGroup{}
	n := 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")
 
}

到此这篇关于golang让map支持高并发(1000万次操作14ms)的文章就介绍到这了,更多相关golang map并发内容请搜索恩蓝小号以前的文章或继续浏览下面的相关文章希望大家以后多多支持恩蓝小号!

原创文章,作者:UCPSM,如若转载,请注明出处:https://www.wangzhanshi.com/n/5957.html

(0)
UCPSM的头像UCPSM
上一篇 2024年12月24日 14:19:49
下一篇 2024年12月26日 13:15:24

相关推荐

  • golang高并发之本地缓存详解

    一、使用场景 试想一个场景,有一个配置服务系统,里面存储着各种各样的配置,比如直播间的直播信息、点赞、签到、红包、带货等等。这些配置信息有两个特点: 并发量可能会特别特别大,试想一…

    2024年12月17日
  • golang slice中常见性能优化手段总结

    这篇文章不会讨论缓存命中率和SIMD,我知道这两样也和slice的性能相关,但前者我认为是合格的开发者必须要了解的,网上优秀的教程也很多不需要我再赘述,后者除非性能瓶颈真的在数据吞…

    Golang 2024年12月17日
  • Go语言的io输入输出流方式

    Go语言的io输入输出流 Go语言的输入输出流不如其他语言那么直观,由于是通过实现接口方法的隐式继承所以比较抽象,今天具体介绍一下go语言的输入输出流。 go语言输入输出在io库中…

    2024年12月17日
  • Go中Vendo机制的使用

    1. 介绍 自 Go 1.6 起,vendor 机制正式启用,它允许把项目的依赖放到一个位于本项目的 vendor 目录中,这个 vendor 目录可以简单理解成私有的 GOPAT…

    Golang 2024年12月17日
  • golang整合日志zap的实现示例

    在许多Go语言项目中,我们需要一个好的日志记录器能够提供下面这些功能: 能够将事件记录到文件中,而不是应用程序控制台; 日志切割-能够根据文件大小、时间或间隔等来切割日志文件; 支…

    Golang 2024年12月17日
  • Go语言基本类型转换的实现示例

    在Go语言编程中,类型转换是一个常见的操作,它允许我们在不同的数据类型之间转换值。Go语言提供了几种方式来进行类型转换,以满足不同的编程需求。本文将详细介绍Go语言中的基本类型转换…

    Golang 2024年12月17日
  • 如何使用go实现创建WebSocket服务器

    使用Go语言创建WebSocket服务器可以利用现有的库来简化开发过程。gorilla/websocket 是一个非常流行且功能强大的库,适用于Go语言的WebSocket应用。下…

    Golang 2024年12月17日
  • Go语言中的通道chan使用指南

    Go 语言的通道(chan)是实现并发编程的核心工具之一,它为 goroutine 之间的通信提供了一种简单而高效的方式。在这篇文章中,我们将深入探讨通道的使用姿势,包括基本操作、…

    Golang 2024年12月17日
  • go语言多线程操作实现

    引言 多线程是一种编程概念,它允许操作系统同时处理多个任务。在多线程环境中,每个线程都代表了一个任务的执行流程。这些线程可以同时运行,使得程序能够更有效地利用计算资源,特别是在多核…

    Golang 2024年12月17日
  • Golang工作池的使用实例讲解

    一、概念 我们可以将工作池理解为线程池。线程池的创建和销毁非常消耗资源,所以专门写一个pool,每次用过的线程池再放回pool中而不是销毁。不过在Go语言中不会使用系统的线程,而是…

    Golang 2024年12月26日

发表回复

登录后才能评论