【问题标题】:Which clojure parallelism technique to use when searching a growing solution space?搜索不断增长的解决方案空间时使用哪种 clojure 并行技术?
【发布时间】:2017-11-02 13:51:59
【问题描述】:

在 Clojure 中,当处理的每个作业都可以完全孤立地发生并且可能生成需要评估的其他作业列表时,在 Clojure 中进行并行处理的正确方法是什么?

我的实际问题是营养计算问题,但我将把它放在 Chess 的形式中,它与我的计算具有相同的问题空间特征。

假设,例如,我试图在国际象棋游戏中找到将棋的所有动作。在搜索棋盘状态时,我会从 20 个可能的状态开始,每个状态代表不同的可能开局动作。每一个都需要被评估、接受或拒绝,然后对于每个被接受的动作,将创建一个新的工作列表,代表所有可能的下一步动作。作业如下所示:

initial: '([] proposed-move)
accepted: '([move] proposed-response)
          '([move move] proposed-response)

每次计算的结果是要评估的状态数量会增加,并且每个状态都可以在与所有其他状态完全隔离的情况下进行评估。

我正在使用的解决方案是这样的:

; a list of all final solutions, each of which is a sequence of moves
(def solutions (agent []))
; a list of all jobs pending evaluation
(def jobs (agent []))

鉴于这些定义,我将拥有一个 java 线程池,每个线程都会从作业代理请求一个作业(并等待该请求得到满足)。然后它将运行计算,生成解决方案列表和可能的解决方案。最后,它将解决方案发送给解决方案代理,并将可能的解决方案发送给工作代理。

在这种情况下,使用代理和线程的组合是最惯用的方法吗?我什至可以按照我提议的方式从作业队列中取出数据吗?

或者我的作业应该是 java.util.concurrent.LinkedBlockingQueue,如 Producer consumer with qualifications 中所述?

【问题讨论】:

  • 或者,也许是一堆future而不是线程池?
  • 这看起来像是一个非常有状态的实现。我建议以更实用的方式来做,并依靠核心功能来解决并发问题
  • 这是一个图搜索问题,不是 clojure 问题;你掩盖了最重要的部分。一旦你决定了搜索算法、启发式等,然后回来询问如何在 clojure 中实现它。

标签: clojure parallel-processing


【解决方案1】:

您可以通过以下方法做到这一点:

  • pmap 的重复应用(提供集合中所有元素的并行处理)
  • pmap 中使用的函数返回元素列表。可以是零个、一个或多个元素,然后将在下一次迭代中处理
  • 结果与concat 重新组合
  • 您可以根据需要重复处理列表多次,可能会将结果存储在原子中。

示例代码可能如下所示

(def jobs (atom '(1 10 100)))

(defn process-element [value]
  (if (< (rand) 0.8)
    [(inc value)]
    []))

(defn do-processing []
  (swap! jobs 
         (fn [job-list] (apply concat (pmap process-element job-list)))))

(while (seq @jobs)
  (prn @jobs)
  (do-processing))

Whick 可以产生如下输出:

(1 10 100)
(2 11 101)
(3 12 102)
(4 13 103)
(5 14 104)
(6 15 105)
(7 106)
(107)
(108)
(109)
nil

请注意,您需要小心一点,以确保您的算法终止!在示例中,元素会随着时间的推移而消失,但如果您的搜索空间正在增长,那么您可能希望应用时间限制,而不是仅仅使用 (while ... ) loop

【讨论】:

  • 在 Paul Lam 上面提到“功能性更强的实现”之后,我开始考虑递归地使用 pmap 并利用惰性。我将永远有一个深度截止,所以我知道我永远不会超过堆栈。我喜欢这个解决方案和你的解决方案。
【解决方案2】:

您使用代理和线程的方法似乎非常接近(我认为)惯用的clojure。

为了让它更像“clojure”,我唯一要改变的是使用pmap 来迭代存储在代理中的队列。使用 pmap 而不是您自己的线程池将节省您管理线程池的工作,因为 pmap 已经使用 clojure 的线程池,该线程池已针对当前处理器数量正确初始化。它还可以帮助您利用序列分块(这可能会有所帮助)。

【讨论】:

    【解决方案3】:

    您也可以使用频道。也许是这样的:

    (def jobs (chan))
    (def solutions (chan))
    (def accepted-solutions (atom (vector)))
    
    (go (loop [job (<! jobs)]
          (when job
            (go (doseq [solution (process-job-into-solutions job)]
                  (>! solutions)))
            (recur (<! jobs)))))
    
    (go (loop [solution (<! solutions)]
          (when (acceptable? solution)
            (swap! accepted-solutions conj solution)
            (doseq [new-job (generate-new-jobs solution)]
              (>! jobs))
            (recur (<! solutions)))))
    
    (>!! jobs initial-job)
    

    【讨论】:

      猜你喜欢
      • 2010-09-12
      • 2021-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-09
      • 1970-01-01
      • 2010-11-27
      相关资源
      最近更新 更多