因此,我使用congomongo (接近尾声的fetch函数)从mongodb集合中提取一些文档。我希望将选项传递给fetch调用,这样我就可以执行类似(posts :limit 1)的操作,并让{:limit 1}传递给fetch。我正在用@posts做手滚"memoization“,因为我想要能够重置缓存,据我所知,这不能用clojure.core/memoize来完成。
现在,我在这里看到的问题是,(fetch :posts options)调用不是微不足道的,如果dosync必须重试事务,我真的不想破坏我的数据存储。我完全是一个clojure/fp新手,我不确定如何解决这个问题。此外,由于我是一个菜鸟,如果我在这里做了任何让你感到畏缩的事情,我很想知道如何正确地编写这篇文章。
(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)))))发布于 2011-03-31 07:42:10
我不相信你的事务块((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位可以避免两个并发事务都提交alter的情况。
关于fetch-posts的重试,是的,这是可能发生的,尽管您的缓存解决方案避免了大多数重试。不过,我不确定有没有办法在不锁定的情况下绕过它。通常,对于事务中的I/O内容,您会将其分包给代理,但事务的成功取决于fetch的返回值,所以我不清楚这是如何工作的。
发布于 2011-04-01 04:52:05
所以你引入ref是因为你不想随着时间的推移而耗尽内存,因为仅仅在fetch-post周围使用memoize迟早会导致这种情况,对吧?
也许你可以尝试另一种方法:让fetch-post是“纯”的,没有内存。在这种情况下,有人可以盲目地调用fetch-posts,而不必害怕OutOfMemoryExceptions。实际上,对于某些用例,在调用代码的本地“缓存值”可能就足够了。
但故事不会在这里结束,否则我不会花时间来回答:-):通过使用clojure.core/binding重新绑定fetch-post,您可以非常容易地让您的“及时本地化”memoize :从那时起,调用堆栈中同一线程中的所有代码都将从绑定的memoized fetch-posts中受益。如果您使用的是clojure 1.3 alpha,则需要通过:dynamic元数据显式地将fetch-posts声明为rebindable。
;; 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)] ...)我最后的猜测是,在你的原始版本中,你会想要在帖子中“记忆”,通过一个键来索引帖子,这将是选项序号,对吗?也许你的代码是不正确的?(或者您假设fetch-posts总是反复使用相同的参数调用?)
另一个想法。使用代理来序列化对posts的写访问,然后确保对fetch的调用仅在它为nil时完成:
(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))发布于 2011-04-01 15:28:25
另一种可能对将大量计算转移到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而不是post数据。在这个示例中,这可能是a)牵强的,b)可能是一个无论如何都必须考虑的情况,但可能有其他用例,这在更关键的代码中引入了一个微妙的竞争条件。
tl;dr:小心你做的事情!Clojure并不是神奇的线程安全的。对您的解决方案进行彻底的推理,并了解其影响。
https://stackoverflow.com/questions/5493500
复制相似问题