给定以下代理,这是一个简单的缓存机制:
type CacheMsg<'a,'b> = Add of 'a * 'b | ForceFlush
type CacheAgent<'a, 'b when 'a : comparison>(size:int, flushCont:Map<'a, 'b> -> unit) =
let agent = MailboxProcessor.Start(fun inbox ->
let rec loop (cache : Map<'a, 'b>) = async {
let inline flush() =
flushCont cache
loop Map.empty
if cache.Count > size then return! flush()
let! msg = inbox.Receive()
match msg with
| Add (key, value) ->
if cache.ContainsKey key then
return! loop cache
else return! loop (cache.Add(key, value))
| ForceFlush -> return! flush() }
loop Map.empty)
member x.AddIfNotExists key value = Add(key,value) |> agent.Post
member x.ForceFlush() = agent.Post ForceFlush
Run Code Online (Sandbox Code Playgroud)
此代理将继续占用内存(似乎flushCont在调用时未释放内存).
给出相同的代码,但稍有改动:
type CacheMsg<'a,'b> = Add of 'a * 'b | ForceFlush
type CacheAgent<'a, 'b when 'a : comparison>(size:int, flushCont:Map<'a, 'b> -> unit) =
let agent = MailboxProcessor.Start(fun inbox ->
let rec loop (cache : Map<'a, 'b>) = async {
let inline flush() =
flushCont cache
loop Map.empty
let! msg = inbox.Receive()
match msg with
| Add (key, value) ->
if cache.ContainsKey key then
return! loop cache
else
let newCache = cache.Add(key, value)
if newCache.Count > size then
return! flush()
else return! loop (cache.Add(key, value))
| ForceFlush -> return! flush() }
loop Map.empty)
member x.AddIfNotExists key value = Add(key,value) |> agent.Post
member x.ForceFlush() = agent.Post ForceFlush
Run Code Online (Sandbox Code Playgroud)
我已经将决定何时刷新的表达式移动到了union的情况中Add.这导致内存按预期释放.
第一种方法有什么问题,因为它会泄漏内存?
Mar*_*ann 14
第一个版本不是尾递归.
它不是尾递归,因为这个表达式不是函数中的最后一个表达式:
if cache.Count > size then return! flush()
Run Code Online (Sandbox Code Playgroud)
在表达之后,你打电话
let! msg = inbox.Receive()
Run Code Online (Sandbox Code Playgroud)
所以flush()电话不是最后发生的事情.在flush完成隐式的递归调用之后,执行将需要返回到您调用的下一个表达式inbox.Receive().这意味着上下文必须将先前的调用保留在堆栈上,因为递归不在尾部位置:还有更多工作要做.
在第二示例中,所有呼叫flush和loop在尾部位置上.
如果你来自C#背景,你会倾向于认为return! flush()退出该功能,但事实并非如此.唯一的理由
if cache.Count > size then return! flush()
Run Code Online (Sandbox Code Playgroud)
甚至编译没有相应的else分支是因为表达式返回unit.这意味着then分支内的代码不会真正退出函数 - 它只是在分支中执行工作(在本例中flush()),然后继续执行后续表达式.