去goroutine锁定并解锁

时间:2016-06-27 17:13:16

标签: go synchronization

我一直在阅读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()
}

我在这里表现得很安全,或者当我只想阅读时我还需要锁定解锁?

1 个答案:

答案 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)
}

Runnable version