【问题标题】:Best way to remove item in a list for an atom in Clojure在 Clojure 中删除原子列表中的项目的最佳方法
【发布时间】:2013-07-19 12:49:14
【问题描述】:

我正在使用 server.socket 将数据流式传输到多个客户端,server.socket 为每个客户端连接使用线程。 我目前有这样的事情:

(def clients (atom ())) ; connected clients defined globally for that namespace

(swap! clients conj a)  ; adds a client (which is an atom itself as well), this is in a function that is run on the client's thread

;I want to better the process of removing a client!
(dosync (reset! clients (remove #{a} @clients))) ; removes client from list, run in a function on the client's thread

我运行一个通过每个客户端运行并获取内容的函数,它在多个客户端线程中的每一个上处于无限循环中,因此它同时运行:

(doseq [c @clients]
  (print ((deref c) :content))
  (flush))

我得出的结论是,在线程中使用 Atom 确实使程序运行顺畅并允许非阻塞读取,所以我对此很满意,除了我觉得重置全局客户端的 Atom 只是为了删除一个列表中的单个客户是一个糟糕的举动。有没有更合适的方法来使用交换来完成这个! ?我为客户端原子选择了列表,因为我在每个连接的客户端上运行 doseq 以获取内容并将其刷新到输出流套接字。

【问题讨论】:

    标签: multithreading clojure atomic


    【解决方案1】:

    避免取消引用 swap!reset! 中的原子。

    这里swap! 将为您提供所需的一切。它需要一个接收当前值的函数,您可以将其用于更新:

    (def clients (atom '(:a :b :c :d)))
    (swap! clients (fn [s] (remove #{:a} s)))
    

    您可能习惯于看不到 swap! 的函数参数,因为 swap! 会将函数应用于提供的任何其他参数,因此如果它们的顺序正确,例如如果我们使用 set for clients,我们可以

    (def clients (atom #{:a :b :c :d}))
    (swap! clients disj :a)
    

    【讨论】:

    • 哦,我真的很喜欢,非常感谢。我也不想引用客户端,但想不出另一种方法来执行此操作。谢谢!
    • 是的,我在网上找到的大部分内容都是关于哈希图和除列表之外的大部分内容:-\
    • (swap!clients (partial remove #{:a})) 使 imo 更清晰。
    【解决方案2】:

    当然,您可以使用swap!,请参阅 A. Webb 的回答。

    您可能需要考虑将客户存储在列表中是否是最佳选择;集合或地图将是更自然的选择(用于disj / dissoc)。 (除非客户端的数量总是非常少,在这种情况下,使用可用的最简单的数据结构可能是有意义的。)

    另外,dosync 在这里什么也不做。 dosync 用于 Refs(和 altercommuteref-setensure)。

    我还要指出,如果你运行这样的循环:

    (doseq [c @clients]
       ...)
    

    那么在输入doseq 表单时,无论swap!s 到clients Atom 可能在此期间发生了什么,它都会循环遍历clients 的值。并不是说这可能是个问题,只是要记住一些事情。

    要记住的另一件事是,Clojure 的引用类型旨在 (1) 保存不可变数据,(2) 使用纯函数进行更新(在 swap! / alter / send 和朋友中)。将原子放入原子中断(1);这可能不是问题,但您有责任确保它不是问题。 (破坏(2)几乎总是一个问题,除非在特殊情况下,例如您完全希望在失败的 CAS 上再次打印调试跟踪等。)

    【讨论】:

    • 感谢您的输入,我希望 dosync 在引用客户端原子时会有所帮助。在更仔细地阅读了这个函数之后,我明白了你在说什么。我选择了 List,因为我从多个线程连续运行了一个 doseq,看起来很自然,而不是从向量或映射中创建一个 seq。我觉得我在某个地方读到过,除了列表之外的任何东西都会对性能造成小幅影响。
    • 实际上,由于分块,向量上的doseq 特别快。在场景或地图上——当然,可能会慢一些,但这没什么大不了的(如果你的dosync 身体有任何作用,它肯定会占主导地位)。一组中的disj 将比长列表中的remove 快得多。
    • 我明白,我发现在doseq 阶段获得一个“快照”,我围绕doseq 连续运行循环,因此每次运行时它都应该引用新值,它确实如此。跨度>
    • 原子中的原子如何打破不变性?感谢您的意见,因为我是新手。基本上我有 1 个包含所有客户端原子的原子。称为 Clients 的主原子仅在新客户端加入或断开连接时更新。每个客户端自己用更新的信息更新他们自己的原子。对我来说,这对于我想要完成的事情是有意义的——在所有相关人员之间共享所有数据。
    • @scape 原子本身是可变的,但它们的内容是不可变的。原子中的原子意味着外部原子将具有可变的内容,即内部原子。这会破坏这一点——原子旨在提供对状态的受控访问,但在这种情况下,可以通过改变内部原子而不咨询外部原子来改变外部原子的状态。第二个问题是swap!等可能在幕后多次发出,所以应该没有副作用。一个副作用的例子是改变状态。存在非常细微错误的风险。
    猜你喜欢
    • 1970-01-01
    • 2010-11-23
    • 2016-08-09
    • 1970-01-01
    • 2010-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-12
    相关资源
    最近更新 更多