所以我使用congomongo(接近结尾的fetch函数)从mongodb集合中提取一些文档。我想将选项传递给fetch调用,因此我可以执行类似(posts :limit 1)
的操作并将{:limit 1}
传递给fetch。我正在使用@posts
进行手动“memoization”,因为我希望能够重置缓存,我的理解不能用clojure.core/memoize
完成。
现在,我在这里看到的问题是(fetch :posts options)
调用非常简单,如果dosync必须重试事务,我真的不想锤击我的数据存储区。我虽然是一个完全的clojure / fp noob,我不知道如何解决这个问题。此外,由于我是一个菜鸟,如果我在这里做任何让你感到畏缩的事情,我很想知道如何正确地写这个。
(def posts (ref nil))
(defn reset-posts [] (dosync alter posts nil))
(defn fetch-posts [& options]
(let [options (apply array-map options)]
(or @posts
(dosync alter posts (fetch :posts options)))))
答案 0 :(得分:6)
我不相信你的交易阻止((dosync alter
...)符合你的想法!
user=> (def posts (ref nil))
#'user/posts
user=> (dosync (ref-set posts [1 2 3 4 5]))
[1 2 3 4 5]
user=> @posts
[1 2 3 4 5]
user=> (dosync alter posts nil)
nil
user=> @posts
[1 2 3 4 5]
在reset-posts
中,您可能需要(dosync (ref-set posts nil))
,而在fetch-posts
中,语法修复将为(dosync (ref-set posts (fetch :posts options)))
。
然而,fetch-posts
中存在竞争条件,即行为检查。可能不是那么大的交易;不确定谁使用fetch-posts
,但在事务中移动or @posts
位将避免2个并发事务最终提交更改的情况。
关于fetch-posts
的重试,是的,虽然你的缓存解决方案避免了大部分问题,但可能会发生这种情况。不过,我不确定它是否有办法绕过它。通常在事务中使用I / O内容将其存储到代理,但事务的成功取决于fetch
的返回值,因此我不清楚它是如何工作的。
答案 1 :(得分:1)
所以你引入了引用,因为你希望能够在时间过去时不会炸掉内存,因为只是在fetch-posts周围使用memoize可能会导致这个,迟早,对吗?
也许你可以尝试另一种方法:让fetch-posts变得“纯粹”,无记忆。在这种情况下,有人可以无视地调用fetch-posts,而不必担心OutOfMemoryExceptions。 实际上,对于某些用例来说,在调用代码的本地“缓存值”可能就足够了。
但故事并没有在这里结束,或者我不会花时间回答:-):你可以通过使用clojure.core / binding重新绑定fetch-posts来轻松地让你的“及时本地化”备忘录:来自然后,调用堆栈中同一线程中的所有代码都将受益于绑定的memoized fetch-posts。 如果您正在使用clojure 1.3 alpha,则需要通过:dynamic metadata将fetch-posts var声明为可重新绑定。
;; most simple definition
(defn ^:dynamic fetch-posts [& options]
(let [options (apply array-map options)]
(fetch :posts options)))
;; a la carte caching by the calling code (lexically scoped)
(let [posts (apply fetch-posts options)] ...)
;; a la carte caching by the calling code (dynamically scoped)
(binding [fetch-posts (memoize fetch-posts)] ...)
我的最后一个猜测是,您希望在原始版本的帖子中“记忆”,通过一个密钥索引帖子,这将是seq的选项,对吧?有些人可能你的代码不对吗? (或者你假设fetch-posts总是会一遍又一遍地用相同的args调用?)
另一个想法。使用代理序列化对帖子的写访问权限,然后确保只有在nil时才会调用fetch:
(def posts (agent nil))
(defn reset-posts [] (send posts (constantly nil)))
(defn fetch-posts [& options]
(let [options (apply array-map options)]
(send-off posts #(or % (fetch :posts options)))
(await-for (Long/MAX_VALUE) posts)
@posts))
答案 2 :(得分:1)
在dosync
之外移动大量计算可能有用的另一种方法是使用delay
。
(defn fetch-posts
[& options]
@(dosync (or @posts (ref-set posts (delay (apply fetch :posts options))))))
另请注意,您的原始代码不是线程安全的,因为您访问dosync
之外的ref并在dosync
之后根据此值对其进行修改。但是deref
和dosync
之间的值可能已经发生了变化。例如。另一个并行调用fetch-posts
的线程。
此外,代理方法也值得怀疑,因为您无法可靠地阅读代理。您获得的值是一致的,但访问不同步。考虑一下Laurent的例子:在await-for
和deref
之间,另一个线程可能已经调用了reset-posts
,而您获得了nil
而不是帖子数据。在这个例子中,这可能是a)远程提取和b)可能是一个必须考虑的情况,但可能有其他用例,这会在更关键的代码中引入微妙的竞争条件。
tl;博士:小心你的所作所为! Clojure不是神奇的线程安全。彻底了解您的解决方案,并了解其影响。