我在Clojure有两个客户航班预订实施。第一个是顺序的,第二个是我尝试并行化。我的理解是并行实现应该更快。 顺序实现使用原子,而并行实现使用refs。输入只是两个集合 - 一组航班和一组客户。预订流程涉及根据客户预算和目的地调整航班。还有一个调整航班价格的销售流程。请注意,销售流程以及客户和航班集合对于两种实施都是相同的。
以下是顺序实施。
(ns flight-reservation
(:require [clojure.string]
[clojure.pprint]
#_[input-simple :as input]
[input-random :as input]))
(def logger (agent nil))
(defn log [& msgs] (send logger (fn [_] (apply println msgs))))
;(defn log [& msgs] nil)
(def flights
"All flights are encapsulated in a single atom in this implementation.
You are free to change this to a more appropriate mechanism."
(atom []))
(defn initialize-flights [initial-flights]
"Set `flights` atom to the `initial-flights`."
(reset! flights initial-flights))
(defn print-flights [flights]
"Print `flights`."
(letfn [(pricing->str [pricing]
(->> pricing
(map (fn [[p a t]] (clojure.pprint/cl-format nil "$~3d: ~3d ~3d" p a t)))
(clojure.string/join ", ")))]
(doseq [{:keys [id from to pricing]} flights]
(println (clojure.pprint/cl-format nil "Flight ~3d from ~a to ~a: ~a"
id from to (pricing->str pricing))))))
(defn- update-pricing [flight factor]
"Updated pricing of `flight` with `factor`."
(update flight :pricing
#(map (fn [[p a t]] [(* p factor) a t]) %)))
(defn start-sale [flight-ids]
"Sale: -20% on `flight-ids`."
(log "Start sale for flights" flight-ids)
(swap! flights
(fn [old-flights]
(vec (map
(fn [flight]
(if (contains? flight-ids (:id flight))
(update-pricing flight 0.80)
flight))
old-flights)))))
(defn end-sale [flight-ids]
"End sale: +25% (inverse of -20%) on `flight-ids`."
(log "End sale")
(swap! flights
(fn [old-flights]
(vec (map
(fn [flight]
(if (contains? flight-ids (:id flight))
(update-pricing flight 1.25)
flight))
old-flights)))))
(defn sort-pricing [pricing]
"Sort `pricing` from lowest to highest price."
(sort-by first pricing))
(defn filter-pricing-with-n-seats [pricing seats]
"Get `pricing` for which there are at least `seats` empty seats available."
(filter #(>= (second %) seats) pricing))
(defn lowest-available-price [flight seats]
"Returns the lowest price in `flight` for which at least `seats` empty seats
are available, or nil if none found."
(-> (:pricing flight) ; [[price available taken]]
(filter-pricing-with-n-seats seats)
(sort-pricing)
(first) ; [price available taken]
(first))) ; price
(defn- find-flight [flights customer]
"Find a flight in `flights` that is on the route and within the budget of
`customer`. If a flight was found, returns {:flight flight :price price},
else returns nil."
(let [{:keys [_id from to seats budget]}
customer
flights-and-prices
; flights that are on the route and within budget, and their price
(for [f flights
:when (and (= (:from f) from) (= (:to f) to))
:let [lowest-price (lowest-available-price f seats)]
:when (and (some? lowest-price) (<= lowest-price budget))]
{:flight f :price lowest-price})
cheapest-flight-and-price
(first (sort-by :price flights-and-prices))]
cheapest-flight-and-price))
(defn- book [flight price seats]
"Updates `flight` to book `seats` at `price`."
(update flight :pricing
(fn [pricing]
(for [[p a t] pricing]
(if (= p price)
[p (- a seats) (+ t seats)]
[p a t])))))
(defn- process-customer [flights customer]
"Try to book a flight from `flights` for `customer`, returning the updated
flight if found, or nil if no suitable flight was found."
(if-let [{:keys [flight price]} (find-flight flights customer)]
(let [updated-flight (book flight price (:seats customer))]
(log "Customer" (:id customer) "booked" (:seats customer)
"seats on flight" (:id updated-flight) "at $" price " (< budget of $"
(:budget customer) ").")
updated-flight)
(do
(log "Customer" (:id customer) "did not find a flight.")
nil)))
(def finished-processing?
"Set to true once all customers have been processed, so that sales process
can end."
(atom false))
(defn process-customers [customers]
(Thread/sleep 100)
"Process `customers` one by one."
(doseq [customer customers]
(swap! flights
(fn [flights]
(if-let [updated-flight (process-customer flights customer)]
(assoc flights (:id updated-flight) updated-flight)
flights))))
(reset! finished-processing? true))
(defn sales-process []
"The sales process starts and ends sales periods, until `finished-processing?`
is true."
(loop []
(let [discounted-flight-ids (->> input/flights
(map :id)
shuffle
(take input/NUMBER_OF_DISCOUNTED_FLIGHTS)
set)]
(Thread/sleep input/TIME_BETWEEN_SALES)
(start-sale discounted-flight-ids)
(Thread/sleep input/TIME_OF_SALES)
(end-sale discounted-flight-ids))
(if (not @finished-processing?)
(recur))))
(defn main []
(initialize-flights input/flights)
(let [f1 (future (time (process-customers input/customers)))
f2 (future (sales-process))]
@f1
@f2)
(println "Flights:")
(print-flights @flights))
(main)
(shutdown-agents)
以下是并行实现:
(ns flight-reservation
(:require [clojure.string]
[clojure.pprint]
#_[input-simple :as input]
[input-random :as input]))
(def N-THREADS 32)
(def logger (agent nil))
(defn log [& msgs] (send logger (fn [_] (apply println msgs))))
;(defn log [& msgs] nil)
(def flights
"All flights are encapsulated in a single atom in this implementation.
You are free to change this to a more appropriate mechanism."
(ref []))
(defn initialize-flights [initial-flights]
"Set `flights` atom to the `initial-flights`."
(dosync(ref-set flights initial-flights)))
(defn print-flights [flights]
"Print `flights`."
(letfn [(pricing->str [pricing]
(->> pricing
(map (fn [[p a t]] (clojure.pprint/cl-format nil "$~3d: ~3d ~3d" p a t)))
(clojure.string/join ", ")))]
(doseq [{:keys [id from to pricing]} flights]
(println (clojure.pprint/cl-format nil "Flight ~3d from ~a to ~a: ~a"
id from to (pricing->str pricing))))))
(defn- update-pricing [flight factor]
"Updated pricing of `flight` with `factor`."
(update flight :pricing
#(map (fn [[p a t]] [(* p factor) a t]) %)))
(defn start-sale [flight-ids]
"Sale: -20% on `flight-ids`."
(log "Start sale for flights" flight-ids)
(dosync
(alter flights
(fn [old-flights]
(vec (pmap
(fn [flight]
(if (contains? flight-ids (:id flight))
(update-pricing flight 0.80)
flight))
old-flights))))))
(defn end-sale [flight-ids]
"End sale: +25% (inverse of -20%) on `flight-ids`."
(log "End sale")
(dosync
(alter flights
(fn [old-flights]
(vec (pmap
(fn [flight]
(if (contains? flight-ids (:id flight))
(update-pricing flight 1.25)
flight))
old-flights))))))
(defn sort-pricing [pricing]
"Sort `pricing` from lowest to highest price."
(sort-by first pricing))
(defn filter-pricing-with-n-seats [pricing seats]
"Get `pricing` for which there are at least `seats` empty seats available."
(filter #(>= (second %) seats) pricing))
(defn lowest-available-price [flight seats]
"Returns the lowest price in `flight` for which at least `seats` empty seats
are available, or nil if none found."
(-> (:pricing flight) ; [[price available taken]]
(filter-pricing-with-n-seats seats)
(sort-pricing)
(first) ; [price available taken]
(first))) ; price
(defn- find-flight [flights customer]
"Find a flight in `flights` that is on the route and within the budget of
`customer`. If a flight was found, returns {:flight flight :price price},
else returns nil."
(let [{:keys [_id from to seats budget]}
customer
flights-and-prices
; flights that are on the route and within budget, and their price
(for [f flights
:when (and (= (:from f) from) (= (:to f) to))
:let [lowest-price (lowest-available-price f seats)]
:when (and (some? lowest-price) (<= lowest-price budget))]
{:flight f :price lowest-price})
cheapest-flight-and-price
(first (sort-by :price flights-and-prices))]
cheapest-flight-and-price))
(defn- book [flight price seats]
"Updates `flight` to book `seats` at `price`."
(update flight :pricing
(fn [pricing]
(for [[p a t] pricing]
(if (= p price)
[p (- a seats) (+ t seats)]
[p a t])))))
(defn- process-customer [flights customer]
"Try to book a flight from `flights` for `customer`, returning the updated
flight if found, or nil if no suitable flight was found."
(if-let [{:keys [flight price]} (find-flight flights customer)]
(let [updated-flight (book flight price (:seats customer))]
(log "Customer" (:id customer) "booked" (:seats customer)
"seats on flight" (:id updated-flight) "at $" price " (< budget of $"
(:budget customer) ").")
updated-flight)
(do
(log "Customer" (:id customer) "did not find a flight.")
nil)))
(def finished-processing?
"Set to true once all customers have been processed, so that sales process
can end."
(atom false))
(defn process-customers [customers]
"Process `customers` one by one."
(Thread/sleep 100)
(dosync
(doseq [customer customers]
(alter flights
(fn [flights]
(if-let [updated-flight (process-customer flights customer)]
(assoc flights (:id updated-flight) updated-flight)
flights)))))
(reset! finished-processing? true))
(defn sales-process []
"The sales process starts and ends sales periods, until `finished-processing?`
is true."
(loop []
(let [discounted-flight-ids (->> input/flights
(pmap :id)
shuffle
(take input/NUMBER_OF_DISCOUNTED_FLIGHTS)
set)]
(Thread/sleep input/TIME_BETWEEN_SALES)
(start-sale discounted-flight-ids)
(Thread/sleep input/TIME_OF_SALES)
(end-sale discounted-flight-ids)
(if (not @finished-processing?)
(recur)))))
(defn partitionCustomerInput
[threads customers]
(let [partitions (partition-all
(Math/ceil (/ (count customers) threads)) customers)]
partitions))
(defn main []
(initialize-flights input/flights)
(let [f1 (time (doall (pmap process-customers (partitionCustomerInput N-THREADS input/customers))))
f2 (future (sales-process))]
@f2)
(println "Flights:")
(print-flights @flights))
(main)
(shutdown-agents)
顺序实现比并行实现更快。我不明白为什么在并行实现中,在main中,在将客户集合拆分为等于所需线程数的分区后,我将客户处理与pmap并行化。
答案 0 :(得分:0)
并行性和pmap的问题是它仍然保证返回值排序。这意味着如果你有一个包含8个核心/工作人员的pmap池和一个块,那么整个批次将被阻止。
看看Claypoole的无序pmap'uppmap',其中返回值排序被删除,有利于更快的返回值。在这里,一个阻塞操作仍然会留下7个内核以实现更快的操作。