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

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

报告相同问题?

悬赏问题

  • ¥15 openwrt双栈NAT
  • ¥15 部分网页页面无法显示!
  • ¥15 怎样解决power bi 中设置管理聚合,详细信息表和详细信息列显示灰色,而不能选择相应的内容呢?
  • ¥15 QTOF MSE数据分析
  • ¥15 平板录音机录音问题解决
  • ¥15 请问维特智能的安卓APP在手机上存储传感器数据后,如何找到它的存储路径?
  • ¥15 (SQL语句|查询结果翻了4倍)
  • ¥15 Odoo17操作下面代码的模块时出现没有'读取'来访问
  • ¥50 .net core 并发调用接口问题
  • ¥15 网上各种方法试过了,pip还是无法使用