You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Clojure中含Ref集合的Atom操作及航班预订逻辑问题排查

解决Clojure多线程航班预订应用的Ref事务处理问题

你的核心问题在于对Clojure的ref和atom配合使用的逻辑偏差——你把航班包装成ref的集合存在atom里,但后续操作既没利用ref的事务特性,还错误地用swap!修改整个序列(assoc根本不适用于序列类型)。我们一步步来修正:


1. 重新设计flights的存储结构

当前flights是ref的序列,查找和修改效率低。我们改成以航班ID为键、对应航班ref为值的map,这样能快速定位目标航班:

; 把atom从空序列改成空map
(def flights (atom {}))

(defn initialize-flights [initial-flights]
  (reset! flights 
          (into {} 
                (map (fn [flight] [(:id flight) (ref flight)]) initial-flights))))

2. 修正process-customers的多线程逻辑

不需要用swap!修改整个flights集合——每个航班都是独立的事务性ref,我们直接在事务中修改单个ref即可。用pmap简化多线程处理:

(defn process-customers [customers thread-count]
  (let [partitions (partitionCustomerInput thread-count customers)]
    ; 用pmap给每个分区分配线程,doall强制执行所有future
    (doall (pmap (fn [partition]
                   (doseq [customer partition]
                     (process-customer @flights customer)))
                 partitions)))
  (reset! finished-processing? true))

3. 修正process-customer及辅助函数

现在process-customer接收的是ref的map,我们需要在find-flight中先deref拿到实际航班数据,再用dosync包裹ref的修改操作:

修正find-flight

(defn- find-flight [flight-refs customer]
  (let [{:keys [id from to seats budget]} customer
        flights-and-prices
        (for [[flight-id flight-ref] flight-refs
              :let [flight @flight-ref] ; 先解引用拿到航班数据
              :when (and (= (:from flight) from) (= (:to flight) to))
              :let [lowest-price (lowest-available-price flight seats)]
              :when (and (some? lowest-price) (<= lowest-price budget))]
          {:flight-ref flight-ref :price lowest-price :flight-id flight-id})]
    (first (sort-by :price flights-and-prices))))

修正process-customer(加入事务)

(defn- process-customer [flight-refs customer]
  (if-let [{:keys [flight-ref price flight-id]} (find-flight flight-refs customer)]
    (dosync ; 开启事务,保证多线程下数据一致性
      (let [current-flight @flight-ref
            updated-flight (book current-flight price (:seats customer))]
        (alter flight-ref (constantly updated-flight)) ; 修改ref的值
        (log "Customer" (:id customer) "booked" (:seats customer) "seats on flight" flight-id "at $" price " (< budget of $" (:budget customer) ").")
        updated-flight))
    (do
      (log "Customer" (:id customer) "did not find a flight.")
      nil)))

4. 修正主函数main

现在flights是map,打印时需要取出所有ref的值:

(defn main []
  (initialize-flights input/flights)
  (time (process-customers input/customers 4)) ; 这里4是示例线程数,可替换为你的N-THREADS
  (println "Flights:")
  (print-flights (map (comp deref val) @flights)) ; 解引用所有航班ref并打印
  (shutdown-agents))

(main)

关键逻辑说明

  • ref的事务特性:Clojure的ref必须在dosync事务中修改,这样能自动处理多线程竞态条件——如果两个线程同时修改同一个航班,事务会自动重试,保证数据一致性。
  • map索引ref:相比序列,map通过航班ID快速定位ref,避免遍历整个集合,提升处理效率。
  • 避免不必要的swap!:原来的swap!试图修改整个flights集合,实际上我们只需要修改单个航班的ref,更符合Clojure不可变数据模型的设计思想。

内容的提问来源于stack exchange,提问作者Gakuo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:35:37