dsl36367 2016-06-27 17:13
浏览 19
已采纳

进入goroutine锁定和解锁

I been reading about goroutines and the sync package and my question is... Do I always need to lock unlock when reading writting to data on different goroutines?

For example I have a variable on my server

config := make(map[string]string)

Then on different goroutines I want to read from config. Is it safe to read without using sync or it is not?

I guess writting needs to be done using the sync package. but I am not sure about reading

For example I have a simple in-memory cache system

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()
}

I am acting safe here or I still need to lock unlock when I want to only read?

  • 写回答

1条回答 默认 最新

  • dongzouban9871 2016-06-27 17:44
    关注

    You're diving into the world of race conditions. The basic rule of thumb is that if ANY routine writes to or changes a piece of data that can be or is read by (or also written to) by any number of other coroutines/threads, you need to have some sort of synchronization system in place.

    For example, lets say you have that map. It has ["Joe"] = "Smith" and ["Sean"] = "Howard" in it. One goroutine wants to read the value of ["Joe"]. Another routine is updating ["Joe"] to "Cooper". Which value does the first goroutine read? Depends on which goroutine gets to the data first. That's the race condition, the behavior is undefined and unpredictable.

    The easiest method to control that access is with a sync.Mutex. In your case, since some routines only need to read and not write, you can instead use a sync.RWMutex (main difference is that a RWMutex allows any number of threads to read, as long as none are trying to write). You would bake this into the map using a structure like this:

    type MutexMap struct {
        m map[string]string
        *sync.RWMutex
    }
    

    Then, in routines that need to read from the map, you would do:

    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]
    }
    

    And to write:

    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
    }
    

    Note that both of these could be written as methods of the struct, rather than functions, if desired.


    You can also approach this using channels. You make a controller structure that runs in a goroutine, and you make requests to it over channels. Example:

    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

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥20 delta降尺度方法,未来数据怎么降尺度
  • ¥15 c# 使用NPOI快速将datatable数据导入excel中指定sheet,要求快速高效
  • ¥15 再不同版本的系统上,TCP传输速度不一致
  • ¥15 高德地图点聚合中Marker的位置无法实时更新
  • ¥15 DIFY API Endpoint 问题。
  • ¥20 sub地址DHCP问题
  • ¥15 delta降尺度计算的一些细节,有偿
  • ¥15 Arduino红外遥控代码有问题
  • ¥15 数值计算离散正交多项式
  • ¥30 数值计算均差系数编程