使用共享地图的不错的,惯用的方式

时间:2013-08-12 16:34:45

标签: concurrency go

假设我有一个可以同时访问地图的程序,如下所示:

func getKey(r *http.Request) string { ... }

values := make(map[string]int)

http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
  key := getKey(r)
  fmt.Fprint(w, values[key])
})

http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
  key := getKey(r)
  values[key] = rand.Int()
})

这很糟糕,因为地图写入是非原子的。所以我可以使用读/写互斥锁

func getKey(r *http.Request) string { ... }

values := make(map[string]int)
var lock sync.RWMutex

http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
  key := getKey(r)
  lock.RLock()
  fmt.Fprint(w, values[key])
  lock.RUnlock()
})

http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
  key := getKey(r)
  lock.Lock()
  values[key] = rand.Int()
  lock.Unlock()
})

除了我们直接使用互斥体而不是频道这一事实外,这似乎很好。

实现这一目标的更实际的方法是什么?或者这是一个互联网真正需要的时代吗?

4 个答案:

答案 0 :(得分:11)

我认为这个大小取决于你的表现期望以及最终如何使用这张地图。

当我研究同样的问题时,我发现了这个非常有帮助的article that should answer your question

我个人的回答是,除非您确实需要使用互斥锁,否则您应该默认使用频道。惯用Go的核心点是,如果您坚持使用更高级别的通道功能,则不需要使用互斥锁并担心锁定。记住Go的座右铭:“通过沟通来共享记忆,不要通过共享记忆来沟通。”

还有一个非常详细的介绍,在Mark Summerfield的Go book中建立一个安全地图的同时使用的不同技术。

要突出显示Rob Pike's slide,Go的创建者之一:

  

并发简化了同步

     
      
  • 无需显式同步
  •   
  • 程序的结构是隐式同步的
  •   

当您沿着使用像互斥体这样的原语的路径时,由于您的程序更复杂,因此非常,非常很难做到正确。你被警告了。

这里还引用了Golang site itself

  

许多环境中的并发编程很难实现   实现对共享变量的正确访问所需的微妙之处。   Go鼓励采用不同的方法来传递共享值   在渠道上,实际上,从不主动分享   执行的线程。只有一个goroutine可以访问该值   任何给定的时间。这种方法可以采取太多措施。参考计数   可能最好通过在整数变量周围放置一个互斥量来实现   实例。但作为一种高级方法,使用渠道来控制   访问可以更容易地编写清晰,正确的程序。

答案 1 :(得分:7)

我会说互斥对这个应用程序没问题。将它们包裹在一个类型中,这样你就可以像以后一样改变主意。请注意嵌入sync.RWMutex然后使锁定整齐。

type thing struct {
    sync.RWMutex
    values map[string]int
}

func newThing() *thing {
    return &thing{
        values: make(map[string]int),
    }
}

func (t *thing) Get(key string) int {
    t.RLock()
    defer t.RUnlock()
    return t.values[key]
}

func (t *thing) Put(key string, value int) {
    t.Lock()
    defer t.Unlock()
    t.values[key] = value
}

func main() {
    t := newThing()
    t.Put("hello", 1)
    t.Put("sausage", 2)

    fmt.Println(t.Get("hello"))
    fmt.Println(t.Get("potato"))
}

Playground link

答案 2 :(得分:3)

  • 您不能将锁本身用于消息队列。这就是渠道的用途。

  • 您可以按频道模拟锁定,但这不是通道的用途。

  • 使用锁可以并发安全地访问共享资源。

  • 使用频道进行并发安全消息排队。

使用RWMutex保护地图写入。

答案 3 :(得分:0)

这是一种替代的基于渠道的方法,使用渠道作为互斥机制:

func getKey(r *http.Request) string { ... }

values_ch := make(chan map[string]int, 1)
values_ch <- make(map[string]int)

http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
  key := getKey(r)
  values := <- values_ch
  fmt.Fprint(w, values[key])
  values_ch <- values
})

http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
  key := getKey(r)
  values := <- values_ch
  values[key] = rand.Int()
  values_ch <- values
})

我们最初将资源放在共享频道中。然后goroutines可以借用并返回该共享资源。但是,与RWMutex的解决方案不同,多个读者可以相互阻止。