go concurrency: implement a map

发布时间 2023-12-05 16:58:15作者: PEAR2020

requirement:

实现阻塞读且并发安全的map
GO⾥⾯MAP如何实现key不存在 get操作等待 直到key存在或者超时,保证并发安全

implementation:

package main

import (
    "fmt"
    "sync"
    "time"
)

type sp interface {
    Out(key string, val interface{})
    Rd(key string, timeout time.Duration) interface{}
}
type Synmap struct {
    M   map[string]*Entry
    rwm *sync.RWMutex
}

type Entry struct {
    ch      chan interface{}
    value   interface{}
    isExist bool
}

// initSynmap

func NewSynmap() *Synmap {
    return &Synmap{
        M:   make(map[string]*Entry),
        rwm: &sync.RWMutex{},
    }
}

// save
func (m *Synmap) Out(key string, val interface{}) {
    m.rwm.Lock()
    defer m.rwm.Unlock()
    item, ok := m.M[key]

    if !ok {
        item = &Entry{
            value:   val,
            isExist: true,
        }
        m.M[key] = item
        return
    }
    if !item.isExist { // Rd goroutine is waiting
        if item.ch != nil { // Rd goroutine is stil not timeout
            item.ch <- val
            item.value = val
            item.isExist = true
        }
    } else { // just change the value
        item.value = val
    }
    return

}

// read
func (m *Synmap) Rd(key string, timeout time.Duration) interface{} {
    timer := time.NewTimer(timeout)
    m.rwm.RLock()
    v, ok := m.M[key]
    m.rwm.RUnlock()
    if ok {
        return v.value
    }
    m.rwm.Lock()
    v = &Entry{
        ch: make(chan interface{}),
    }
    m.M[key] = v
    m.rwm.Unlock()
    for {
        select {
        case result := <-v.ch:
            close(v.ch)
            fmt.Println("after waiting....")
            return result
        case <-timer.C:
            fmt.Println("timeout")
            close(v.ch)
            v.ch = nil
            return nil
        }
    }
}

func main() {
    synmap := NewSynmap()
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        fmt.Println("try to read key = 123...")
        fmt.Println(synmap.Rd("123", 1*time.Second))
    }()
    go func() {
        defer wg.Done()
        fmt.Println("start to write key")
        time.Sleep(2 * time.Second)
        synmap.Out("123", "outtttt")
    }()
    wg.Wait()

}