map 的一个神奇的问题

2021-04-06 11:54:56 +08:00
 zkdfbb

使用下面的一段代码来统计链接的访问情况,使用方法就是用 Incr 每次访问加 1 单独测试的时候,比如用 wrk 来压测都挺正常的 神奇的是,一换成 nginx,c.data 里面的 key 就变得不正常,把他打印出来,发现有很多相同的 key 比如本来就只有一个 key 的,他会出现 key: 1, key: 2, key: 1, key: 1 这种,搞不懂。。。

type Counter struct {
	sync.RWMutex
	data map[string]int
}

func (c *Counter) Get(key string) int {
	c.RLock()
	count := c.data[key]
	c.RUnlock()
	return count
}

func (c *Counter) Incr(key string) int {
	c.Lock()
	c.data[key]++
	count := c.data[key]
	c.Unlock()
	return count
}

func (c *Counter) Delete(key string) {
	c.Lock()
	delete(c.data, key)
	c.Unlock()
}
6843 次点击
所在节点    Go 编程语言
76 条回复
skiy
2021-04-06 12:05:50 +08:00
```defer c.RUnlock()```
yuguorui96
2021-04-06 12:08:33 +08:00
你的 nginx 不会是 fork 吧…
zkdfbb
2021-04-06 12:55:39 +08:00
@yuguorui96 nginx 是多进程,但是这个 go 的后端只有一个,应该没影响啊
zkdfbb
2021-04-06 12:56:19 +08:00
@skiy 这个代码简洁了一点~性能下降了一点~
no1xsyzy
2021-04-06 14:04:55 +08:00
至少这里看不出什么问题
能提供最小可复现样本吗?
sujin190
2021-04-06 14:08:53 +08:00
map 不可能多个 key 的,反常规的话也许是不可见字符?要么就是你输出代码有问题
GTim
2021-04-06 14:10:08 +08:00
show 你的完整测试代码
AngryPanda
2021-04-06 14:24:29 +08:00
盲猜这些 key 在不同协程中
Lpl
2021-04-06 14:40:11 +08:00
你这段代码槽点比较多,性能会很差:
1. 可以考虑用 atomic 而不是用锁
2. 可以考虑用管道来做 Producer-Consumer,然后多个协程消费,而不是考虑用锁
3. 实在想加锁,就给某个 key 加锁,而不是给 Counter 对象加锁。锁的粒度太粗了

给个比较完整的代码看看
zkdfbb
2021-04-06 14:48:48 +08:00
@Lpl 批评的对🤣
atomic 的话,由于 id 不是唯一的,好像不太方便
没试过用管道,我直观以为管道性能会更差一点,看来我理解错了,可以试一下
有看到过用分段锁的例子,concurrent-map 这个,不过我测了一下就用一把锁好像也足够用了就先这么用了,结果就出现了这个神奇的问题,完整的代码补上了,你看看能不能看出来哪里的问题🤣
zkdfbb
2021-04-06 14:49:14 +08:00
@AngryPanda 按理加了锁应该不同的协程也是 OK 的
zkdfbb
2021-04-06 14:50:25 +08:00
@no1xsyzy
@GTim

补在后面了,看看能不能发现什么问题
Lpl
2021-04-06 15:02:29 +08:00
脑袋疼..你这个问题出在 init 里边的那个协程了:
accessLog = Counter{data: make(map[string]int)}

里边多了这句话,我理解你这个协程就是想打印下当前已存在的数据?
makdon
2021-04-06 15:38:49 +08:00
@Lpl 我理解他这里的意思是,先保存下来当前的状态,然后重置 counter
这里的问题我理解只是重置全局 counter 的时候没有加锁,所以会丢失部分统计数据,但是不应该会如楼主所说“这里 abcde 就是相同的”

我 review 楼主的代码没有 review 出啥问题, 复现的成本对我来说有点高。
如果按照“这里 abcde 就是相同的” 这里来推断,因为 map 里面的 key 值都是唯一的,所以猜想可能楼主实际上使用的并不是 map[string]int, 而是自定义的结构体 /指针 /浮点数
然后多个不同的 id,虽然内容是一致的,但是 hashkey 不一致
zkdfbb
2021-04-06 15:58:09 +08:00
@makdon 不是的,我用的就是 map[string]int,统计的就是一个字符串 id 的访问次数,hashkey 是一样的
要是正常我就不问了,问就是因为不正常😂
我也感觉光看这段代码应该看不出什么问题,但是事实就是这么猝不及防的发生了,脑阔疼
zkdfbb
2021-04-06 15:58:44 +08:00
@Lpl 这个只是个简化的例子,实际上还需要处理一下,不过意思是一样的
joesonw
2021-04-06 16:05:11 +08:00
accessLog = Counter{data: make(map[string]int)}

没加锁
zkdfbb
2021-04-06 16:17:29 +08:00
@joesonw 是要加个锁,不过不是因为这个,我试过把这句注释掉,也是一样的
joesonw
2021-04-06 16:45:20 +08:00
是打印那的问题吗? 没换行? 现在的打印没有打印 map, 只打了 map 的长度.
zkdfbb
2021-04-06 16:55:30 +08:00
@joesonw 嗯,就是本来只要几百个 key 的,结果 map 长度可以上万,也打印过 map 本身,看到的就是有重复的 key

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/768320

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX