草庐IT

尽管有锁,Go 仍检测到 map 上的并发读写

coder 2024-07-06 原文

我正在编写一个简单的缓存机制,它有一个Add、一个Evict 和一个Search 方法。 Search 目前尚未实现,因此无需担心。

调用 Add 添加数据的 goroutine 数量较多,只有一个 goroutine 运行在 evict 循环中逐出数据。一旦我对它施加一些严重的流量,Go 就会抛出并说在 map metricCache 上有并发的读写访问,但我看不出这是怎么发生的,因为它周围有锁。我正在使用 Go 1.7。

文件mdata/cache.go:

57: func NewCCache() *CCache {
58:     cc := &CCache{
59:         lock:        sync.RWMutex{},
60:         metricCache: make(map[string]*CCacheMetric),
61:         accnt:       accnt.NewFlatAccnt(maxSize),
62:     }
63:     go cc.evictLoop()
64:     return cc
65: }
66:
67: func (c *CCache) evictLoop() {
68:     evictQ := c.accnt.GetEvictQ()
69:     for target := range evictQ {
70:         c.evict(target)
71:     }
72: }
73: 
74: func (c *CCache) Add(metric string, prev uint32, itergen chunk.IterGen) {
75:     c.lock.Lock()
76: 
77:     if ccm, ok := c.metricCache[metric]; !ok {
78:         var ccm *CCacheMetric
79:         ccm = NewCCacheMetric()
80:         ccm.Init(prev, itergen)
81:         c.metricCache[metric] = ccm
82:     } else {
83:         ccm.Add(prev, itergen)
84:     }
85:     c.lock.Unlock()
86: 
87:     c.accnt.AddChunk(metric, itergen.Ts(), itergen.Size())
88: }
89: 
90: func (c *CCache) evict(target *accnt.EvictTarget) {
91:     c.lock.Lock()
92: 
93:     if _, ok := c.metricCache[target.Metric]; ok {
94:         log.Debug("cache: evicting chunk %d on metric %s\n", target.Ts, target.Metric)
95:         length := c.metricCache[target.Metric].Del(target.Ts)
96:         if length == 0 {
97:             delete(c.metricCache, target.Metric)
98:         }
99:     }
100: 
101:     c.lock.Unlock()
102: }

这是错误信息:

metrictank_1    | fatal error: concurrent map read and map write
metrictank_1    | 
metrictank_1    | goroutine 3159 [running]:
metrictank_1    | runtime.throw(0xaade7e, 0x21)
metrictank_1    |       /usr/local/go/src/runtime/panic.go:566 +0x95 fp=0xc4216a7eb8 sp=0xc4216a7e98
metrictank_1    | runtime.mapaccess2_faststr(0x9e22c0, 0xc42031e600, 0xc4210c2b10, 0x22, 0x28, 0xa585d5496)
metrictank_1    |       /usr/local/go/src/runtime/hashmap_fast.go:306 +0x52b fp=0xc4216a7f18 sp=0xc4216a7eb8
metrictank_1    | github.com/raintank/metrictank/mdata/cache.(*CCache).Add(0xc4202fa070, 0xc4210c2b10, 0x22, 0x0, 0xc421875f82, 0x25, 0x25, 0xa585d5496)
metrictank_1    |       /home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:77 +0x63 fp=0xc4216a7f80 sp=0xc4216a7f18
metrictank_1    | runtime.goexit()
metrictank_1    |       /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1 fp=0xc4216a7f88 sp=0xc4216a7f80
metrictank_1    | created by github.com/raintank/metrictank/api.(*Server).getSeries
metrictank_1    |       /home/mst/go/src/github.com/raintank/metrictank/api/dataprocessor.go:442 +0x122b

更新:我用 -race 重新编译,现在我得到了一个不同的错误。这看起来好像 RWMutex 完全无效,因为根据回溯,问题一定出在 evictAdd 方法的组合中。

==================
WARNING: DATA RACE
Read at 0x00c4201c81e0 by goroutine 215:
  runtime.mapaccess2_faststr()
      /usr/local/go/src/runtime/hashmap_fast.go:297 +0x0
  github.com/raintank/metrictank/mdata/cache.(*CCache).Add()
      /home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:77 +0xaa

Previous write at 0x00c4201c81e0 by goroutine 155:
  runtime.mapdelete()
      /usr/local/go/src/runtime/hashmap.go:558 +0x0
  github.com/raintank/metrictank/mdata/cache.(*CCache).evict()
      /home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:97 +0x30e
  github.com/raintank/metrictank/mdata/cache.(*CCache).evictLoop()
      /home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:70 +0xb3

Goroutine 215 (running) created at:
  github.com/raintank/metrictank/api.(*Server).getSeries()
      /home/mst/go/src/github.com/raintank/metrictank/api/dataprocessor.go:442 +0x17c9
  github.com/raintank/metrictank/api.(*Server).getTarget()
      /home/mst/go/src/github.com/raintank/metrictank/api/dataprocessor.go:331 +0x9c3
  github.com/raintank/metrictank/api.(*Server).getTargetsLocal.func1()
      /home/mst/go/src/github.com/raintank/metrictank/api/dataprocessor.go:284 +0xa9

Goroutine 155 (running) created at:
  github.com/raintank/metrictank/mdata/cache.NewCCache()
      /home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:63 +0x12f
  main.main()
      /home/mst/go/src/github.com/raintank/metrictank/metrictank.go:388 +0x246c
==================

最佳答案

我的一位同事找到了答案:

调用 NewCCache() 后,我按值(包括锁)复制了返回的变量,然后在副本上调用了 Add(),同时 evictLoop() go 例程仍然引用旧副本。所以他们在锁的不同副本上操作:)

关于尽管有锁,Go 仍检测到 map 上的并发读写,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41305244/

有关尽管有锁,Go 仍检测到 map 上的并发读写的更多相关文章

  1. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  2. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  4. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  5. ruby-on-rails - Ruby - 如何从 ruby​​ 上的 .pfx 文件中提取公钥、rsa 私钥和 CA key - 2

    我有一个.pfx格式的证书,我需要使用ruby​​提取公共(public)、私有(private)和CA证书。使用shell我可以这样做:#ExtractPublicKey(askforpassword)opensslpkcs12-infile.pfx-outfile_public.pem-clcerts-nokeys#ExtractCertificateAuthorityKey(askforpassword)opensslpkcs12-infile.pfx-outfile_ca.pem-cacerts-nokeys#ExtractPrivateKey(askforpassword)o

  6. 带有 attr_accessor 的类上的 Ruby instance_eval - 2

    我了解instance_eval和class_eval之间的基本区别。我在玩弄时发现的是一些涉及attr_accessor的奇怪东西。这是一个例子:A=Class.newA.class_eval{attr_accessor:x}a=A.newa.x="x"a.x=>"x"#...expectedA.instance_eval{attr_accessor:y}A.y="y"=>NoMethodError:undefinedmethod`y='forA:Classa.y="y"=>"y"#WHATTT?这是怎么回事:instance_eval没有访问我们的A类(对象)然后它实际上将它添加到

  7. ruby-on-rails - rails 上的 ruby : radio buttons for collection select - 2

    我有一个集合选择:此方法的单选按钮是什么?谢谢 最佳答案 Rails3中没有这样的助手。在Rails4中,它是collection_radio_buttons. 关于ruby-on-rails-rails上的ruby:radiobuttonsforcollectionselect,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/18525986/

  8. ruby - 在 ruby​​ 中使用 .try 函数和 .map 函数 - 2

    我需要从json记录中获取一些值并像下面这样提取curr_json_doc['title']['genre'].map{|s|s['name']}.join(',')但对于某些记录,curr_json_doc['title']['genre']可以为空。所以我想对map和join()使用try函数。我试过如下curr_json_doc['title']['genre'].try(:map,{|s|s['name']}).try(:join,(','))但是没用。 最佳答案 你没有正确传递block。block被传递给参数括号外的方法

  9. ruby-on-rails - 获取并发布相同匹配项的请求 - 2

    在我的路线文件中我有:match'graphs/(:id(/:action))'=>'graphs#(:action)'如果是GET请求(工作)或POST请求(不工作),我想匹配它我知道我可以使用以下方法在资源中声明POST请求:post'/'=>:show,:on=>:member但是我怎样才能为比赛做到这一点呢?谢谢。 最佳答案 如果你同时想要POST和GETmatch'graphs/(:id(/:action))'=>'graphs#(:action)',:via=>[:get,:post]编辑默认值可以设置如下match'g

  10. ruby - 将命令行上的变量传递给 Cucumber 测试 - 2

    我正在尝试将cucumber项目的用户名和密码置于版本控制之外。有没有办法在命令行上手动将用户名和密码等变量传递给Cucumber脚本?我的备份计划是将它们放在一个YML文件中,然后将该文件添加到gitignore,这样它们就不会被置于版本控制中。 最佳答案 所以,我看到了您对铁皮人的评论,答案是肯定的。cucumberPASSWORD=my_passwordPASSWORD被设置为环境变量,您可以通过将其引用为ENV['PASSWORD']来使用它的值。例如,browser.text_field(:id=>'pwd').setEN

随机推荐