【问题标题】:truly lazy cache pattern? F#真正的惰性缓存模式? F#
【发布时间】:2014-01-13 22:43:24
【问题描述】:

我有以下类型来实现一个简单的惰性缓存:

module CachedFoo =

let mutable private lastAccess:Option<DateTime> = None

// returns a lazy value that initializes the cache when
// accessed for the first time (safely)
let private createCacheInitialization() =
    lazy(
        let someObject = SomeLongRunningOperation()
        lastAccess <- Option.Some(DateTime.Now)
        someObject
    )

// current cache represented as lazy value
let mutable private currentCache = createCacheInitialization()

// Reset - cache will be re-initialized next time it is accessed
// (this doesn't actually initialize a cache - just creates a lazy value)
let MaybeReset() =
    if (lastAccess.IsSome && DateTime.Now > (lastAccess.Value + TimeSpan.FromSeconds (10.0))) then
        currentCache <- createCacheInitialization()

let GetCache() =
    MaybeReset()
    currentCache.Value

第一个问题:上面的线程安全吗?默认情况下,lazy() 似乎是线程安全的,但我想我需要在 lastAccess 字段的分配周围加锁?

第二个也是最重要的:这是惰性的,因为它的值在有人需要它之前不会被检索,但是,我认为我什至可以通过返回最后一个缓存的对象来做得更惰性,即使在 Reset( ) 被调用,但是在后台启动一个异步线程来调用这个方法。

在 C# 中是这样的:

public SomeObject GetCache() {
    try {
        return currentCache.Value;
    } finally {
        ThreadPool.QueueUserWorkItem(new WaitCallback(MaybeReset));
    }
}

我将如何在 F# 中做到这一点? (如果解决方案使用花哨的异步东西而不是使用 ThreadPool API,则加分)。

【问题讨论】:

  • 超时的原因是什么 - 它似乎并没有节省内存,因为它只在有人真正说他们想要这个值时触发?缓存的结果会以某种方式过时吗?
  • 是的,他们可以,但如果我能够找到一种方法在 F# 中对此进行编码,我将简单地减少超时以解决“陈旧”概率的增加

标签: multithreading asynchronous f# lazy-evaluation c#-to-f#


【解决方案1】:

我认为更新 lastAccess 是线程安全的,原因有两个

  • 您只能在 lazy 内执行此操作,这意味着无论如何它只会更新一次(尽管与 Reset 可能存在更微妙的竞争,我不确定)

  • lastAccess 是单个引用(对Option),因此无论如何都会自动更新

要启动新的“一劳永逸”async 以重新计算值,请执行以下操作:

let GetCache() =
    let v = currentCache.Value // to make sure we get the old one
    async { MaybeReset() } |> Async.Start
    v

【讨论】:

  • 谢谢,但我猜你的意思是调用 Reset() 而不是 createCacheInitialization?
  • 我正在复制你的 C# 所做的,但后来我注意到只是调用了 createCacheInitialization 而没有做任何事情,所以我更新了我的代码以写入 currentCache。你当然可以打电话给Reset
  • 哦,那是我的 C# 代码中的一个错字!如果你不介意,我会更新你的答案,谢谢!
  • 所以您的解决方案实际上有效,但是如果有第二个请求者在处理 MaybeReset() 时尝试检索该值,它将被锁定/排队直到完成,所以我该如何避免这种情况把旧的也归还?
  • 好的,我想我已经找到了一个解决方案,我将支持你的解决方案,并提出我的最终解决方案,其中包括你的部分工作,再次感谢!
【解决方案2】:

感谢 Ganesh 的洞察力,我终于选择了这个解决方案,它不会让第二个请求者在刷新结果时等待结果:

module CachedFoo =

let mutable private lastAccess:Option<DateTime> = None

// returns a lazy value that initializes the cache when
// accessed for the first time (safely)
let private createCacheInitialization() =
    lazy(
        let someObject = SomeLongRunningOperation()
        lastAccess <- Option.Some(DateTime.Now)
        someObject
    )

// current cache represented as lazy value
let mutable private currentCache = createCacheInitialization()

let lockObject = new Object()

let timeout = TimeSpan.FromSeconds (10.0)

// Reset - cache will be re-initialized next time it is accessed
// (this doesn't actually initialize a cache - just creates a lazy value)
let MaybeReset() =
    lock lockObject (fun () ->
        if (lastAccess.IsSome && DateTime.Now > (lastAccess.Value + timeout)) then
            let newCache = createCacheInitialization()
            ignore(newCache.Force())
            currentCache <- newCache
    )

let GetCache() =
    let v = currentCache.Value // to make sure we get the old one
    async { MaybeReset() } |> Async.Start
    v

【讨论】:

    猜你喜欢
    • 2013-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-25
    • 2013-08-30
    • 1970-01-01
    相关资源
    最近更新 更多