我一直在阅读goroutines和同步包,我的问题是......在读取不同goroutine上的数据时,我是否总是需要锁定解锁?
例如我的服务器上有一个变量
config := make(map[string]string)
然后在不同的goroutines上我想从配置中读取。不使用同步读取是否安全,或者不是?
我想使用同步包需要完成写入。但我不确定阅读
例如,我有一个简单的内存缓存系统
type Cache interface {
Get(key string) interface{}
Put(key string, expires int64, value interface{})
}
// MemoryCache represents a memory type of cache
type MemoryCache struct {
c map[string]*MemoryCacheValue
rw sync.RWMutex
}
// MemoryCacheValue represents a memory cache value
type MemoryCacheValue struct {
value interface{}
expires int64
}
// NewMemoryCache creates a new memory cache
func NewMemoryCache() Cache {
return &MemoryCache{
c: make(map[string]*MemoryCacheValue),
}
}
// Get stores something into the cache
func (m *MemoryCache) Get(key string) interface{} {
if v, ok := m.c[key]; ok {
return v
}
return nil
}
// Put retrieves something from the cache
func (m *MemoryCache) Put(key string, expires int64, value interface{}) {
m.rw.Lock()
m.c[key] = &MemoryCacheValue{
value,
time.Now().Unix() + expires,
}
m.rw.Unlock()
}
我在这里表现得很安全,或者当我只想阅读时我还需要锁定解锁?
答案 0 :(得分:8)
你正在潜入竞争条件的世界。基本的经验法则是,如果 ANY 例程写入或更改可由任意数量的其他协同程序/线程读取(或写入)的数据,则需要有适当的同步系统。
例如,假设您有该地图。它有[“Joe”] =“Smith”和[“Sean”] =“Howard”。一个goroutine想要读取[“乔”]的值。另一个例程是将[“Joe”]更新为“Cooper”。第一个goroutine读取的值是多少?取决于哪个goroutine首先获取数据。这就是竞争条件,行为是不明确的,不可预测的。
控制该访问的最简单方法是使用sync.Mutex
。在您的情况下,由于某些例程只需要读取而不是写入,您可以使用sync.RWMutex
(主要区别在于RWMutex
允许任意数量的线程读取,只要没有人尝试来写)。你可以使用这样的结构将其烘焙到地图中:
type MutexMap struct {
m map[string]string
*sync.RWMutex
}
然后,在需要从地图中读取的例程中,您将执行以下操作:
func ReadSomething(o MutexMap, key string) string {
o.RLock() // lock for reading, blocks until the Mutex is ready
defer o.RUnlock() // make SURE you do this, else it will be locked permanently
return o.m[key]
}
写下:
func WriteSomething(o MutexMap, key, value string) {
o.Lock() // lock for writing, blocks until the Mutex is ready
defer o.Unlock() // again, make SURE you do this, else it will be locked permanently
o.m[key] = value
}
请注意,如果需要,这两个都可以写为结构的方法,而不是函数。
您也可以使用频道来解决此问题。您创建一个在goroutine中运行的控制器结构,并通过通道向它发出请求。例如:
package main
import "fmt"
type MapCtrl struct {
m map[string]string
ReadCh chan chan map[string]string
WriteCh chan map[string]string
QuitCh chan struct{}
}
func NewMapController() *MapCtrl {
return &MapCtrl{
m: make(map[string]string),
ReadCh: make(chan chan map[string]string),
WriteCh: make(chan map[string]string),
QuitCh: make(chan struct{}),
}
}
func (ctrl *MapCtrl) Control() {
for {
select {
case r := <-ctrl.ReadCh:
fmt.Println("Read request received")
retmap := make(map[string]string)
for k, v := range ctrl.m { // copy map, so it doesn't change in place after return
retmap[k] = v
}
r <- retmap
case w := <-ctrl.WriteCh:
fmt.Println("Write request received with", w)
for k, v := range w {
ctrl.m[k] = v
}
case <-ctrl.QuitCh:
fmt.Println("Quit request received")
return
}
}
}
func main() {
ctrl := NewMapController()
defer close(ctrl.QuitCh)
go ctrl.Control()
m := make(map[string]string)
m["Joe"] = "Smith"
m["Sean"] = "Howard"
ctrl.WriteCh <- m
r := make(chan map[string]string, 1)
ctrl.ReadCh <- r
fmt.Println(<-r)
}