caching - channel 并发保证

标签 caching go concurrency channel memoization

我正在写一个并发安全的备忘录:

package mu

import (
    "sync"
)

// Func represents a memoizable function, operating on a string key, to use with a Mu
type Func func(key string) interface{}

// Mu is a cache that memoizes results of an expensive computation
//
// It has a traditional implementation using mutexes.
type Mu struct {
    // guards done
    mu   sync.RWMutex
    done map[string]chan bool
    memo map[string]interface{}
    f    Func
}

// Get a string key if it exists, otherwise computes the value and caches it.
//
// Returns the value and whether or not the key existed.
func (c *Mu) Get(key string) (interface{}, bool) {
    c.mu.RLock()
    _, ok := c.done[key]
    c.mu.RUnlock()
    if ok {
        return c.get(key), true
    }

    c.mu.Lock()
    _, ok = c.done[key]
    if ok {
        c.mu.Unlock()
    } else {
        c.done[key] = make(chan bool)
        c.mu.Unlock()

        v := c.f(key)
        c.memo[key] = v

        close(c.done[key])
    }
    return c.get(key), ok
}

// get returns the value of key, blocking on an existing computation
func (c *Mu) get(key string) interface{} {
    <-c.done[key]
    v, _ := c.memo[key]
    return v
}

如您所见,有一个互斥体保护着 done领域,这是使用 向其他 goroutines 发出信号,表明某个键的计算正在等待或已完成。这避免了对同一 key 的重复计算(调用 c.f(key))。

我的问题是关于这段代码的保证;通过确保计算 goroutine 在写入 c.memo 后关闭 channel , 这是否保证其他 goroutines 访问 c.memo[key]在对 <-c.done[key] 的阻塞调用之后保证能看到计算结果?

最佳答案

简短的回答是

我们可以简化一些代码以了解原因的本质。考虑您的 Mu 结构:

type Mu struct {
    memo int
    done chan bool
}

我们现在可以定义 2 个函数,computeread

func compute(r *Mu) {
    time.Sleep(2 * time.Second)
    r.memo = 42
    close(r.done)
}

func read(r *Mu) {
    <-r.done
    fmt.Println("Read value: ", r.memo)
}

这里,compute 是一个计算量大的任务(我们可以通过 sleep 一段时间来模拟)

现在,在 main 函数中,我们启动一个新的 compute go routine,并定期启动一些 read go routines:

func main() {
    r := &Mu{}
    r.done = make(chan bool)
    go compute(r)

    // this one starts immediately
    go read(r)
    time.Sleep(time.Second)

    // this one starts in the middle of computation
    go read(r)
    time.Sleep(2*time.Second)

    // this one starts after the computation is complete
    go read(r)

    // This is to prevent the program from terminating immediately
    time.Sleep(3 * time.Second)
}

在所有三种情况下,我们都打印出计算任务的结果。

Working code here

当您在 go 中“关闭” channel 时,所有等待 channel 结果的语句(包括在 channel 关闭后执行的语句)将被阻塞。因此,只要关闭 channel 的唯一地方是计算备忘录值的地方,您就可以得到保证。

唯一需要注意的地方是确保此 channel 未在代码中的其他任何地方关闭。

关于caching - channel 并发保证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50207652/

相关文章:

asp.net - 使用 webusercontrol 获取输出缓存以在 ASP.net 中工作

ios - AFNetworking 3.0 GET/POST 设置可接受的内容类型并清除图像缓存

function - 函数可以在 Go 中实现接口(interface)吗

go - 尝试呈现模板,出现错误 html/template : "templates/index.html" is undefined

C - 并发问题

java - 并发数据结构的使用示例?

ruby-on-rails - 在初始化程序中设置 cache_store

php - 如何在 .htaccess 中为 CDN 添加利用浏览器缓存?

google-app-engine - 应用程序引擎在go中读取本地文件

c# - 使用 RazorEngine 并发解析 Razor 模板