2013-07-12 11 views
6

Mam dwie różne funkcje na dwóch bardzo dużych zestawach danych, które muszą być przetwarzane, w końcu sprowadzając się do dwóch wartości logicznych. wartości te należy następnie połączyć w celu uzyskania końcowego wyniku. moje pytanie brzmi: jaki jest najlepszy sposób tworzenia wątków, aby dwie długie funkcje działały jednocześnie. moje myśli były czymś w rodzaju, ale szukałem informacji na temat lepszego sposobu, aby to osiągnąć.z Clojure gwintowanie długotrwałe procesy i porównywanie ich zwrotów

+3

Czy takie podejście nie działa? Czy masz z tym jakiś szczególny problem? Wydaje mi się całkiem nieźle :) –

+0

podczas pracy, musiał zakończyć oba bez zwarcia, jeśli się nie udało. Szukałem szybszego sposobu robienia tego samego. – ChadJPetersen

+0

@ChadJPetersen Nie, nie musi kończyć obu; '' i 'zwarcia. Nie oznacza to, że go nie używa, ale jest to oddzielna troska (i możesz go anulować, jeśli chcesz). –

Odpowiedz

3

(podejście Obietnica oparte na górze, core.async podejście oparte niżej. Oba zwarcie na pierwszej wartości falsey.)


Oto wersja wykorzystująca fakt, że pojedyncza obietnica może zostać dostarczona wiele razy (chociaż tylko pierwsza dostawa może przynieść wartość; kolejne dostawy po prostu zwracają nil bez efektów ubocznych).

(defn thread-and 
    "Computes logical conjunction of return values of fs, each of which 
    is called in a future. Short-circuits (cancelling the remaining 
    futures) on first falsey value." 
    [& fs] 
    (let [done (promise) 
     ret (atom true) 
     fps (promise)] 
    (deliver fps (doall (for [f fs] 
          (let [p (promise)] 
          [(future 
           (if-not (swap! ret #(and %1 %2) (f)) 
           (deliver done true)) 
           (locking fps 
           (deliver p true) 
           (when (every? realized? (map peek @fps)) 
            (deliver done true)))) 
          p])))) 
    @done 
    (doseq [[fut] @fps] 
     (future-cancel fut)) 
    @ret)) 

Niektóre testy:

(thread-and (constantly true) (constantly true)) 
;;= true 

(thread-and (constantly true) (constantly false)) 
;;= false 

(every? false? 
     (repeatedly 100000 
        #(thread-and (constantly true) (constantly false)))) 
;;= true 

;; prints :foo, but not :bar 
(thread-and #(do (Thread/sleep 1000) (println :foo)) 
      #(do (Thread/sleep 3000) (println :bar))) 

Putting pomysły Artura i A. Webb wraz, można użyć do core.async i wyniki razem, gdy powrócił zwarcie na pierwszej wartości falsey :

(defn thread-and 
    "Call each of the fs on a separate thread. Return logical 
    conjunction of the results. Short-circuit (and cancel the calls 
    to remaining fs) on first falsey value returned." 
    [& fs] 
    (let [futs-and-cs 
     (doall (for [f fs] 
       (let [c (chan)] 
        [(future (>!! c (f))) c])))] 
    (loop [futs-and-cs futs-and-cs] 
     (if (seq futs-and-cs) 
     (let [[result c] (alts!! (map peek futs-and-cs))] 
      (if result 
      (recur (remove #(identical? (peek %) c) 
          futs-and-cs)) 
      (do (doseq [fut (map first futs-and-cs)] 
        (future-cancel fut)) 
       false))) 
     true)))) 

test z (constantly false) i (constantly true):

(thread-and (constantly true) (constantly true)) 
;= true 
(thread-and (constantly true) (constantly false)) 
;= false 

;;; etc. 

Należy również pamiętać, że zwarcie robi rzeczywiście praca:

;;; prints :foo before returning false 
(thread-and #(do (Thread/sleep 3000) false) 
      #(do (Thread/sleep 1000) (println :foo))) 

;;; does not print :foo 
(thread-and #(do (Thread/sleep 3000) false) 
      #(do (Thread/sleep 7000) (println :foo))) 
+0

Dostępne również w tym [gist] (https://gist.github.com/michalmarczyk/5988137). –

+0

Czy można to zrobić bez core.async? –

+0

@event_jr: Właściwie to, po prostu zredagowano odpowiedź tak, aby zawierała podejście oparte na obietnicach. Dostępne również w tym [gist] (https://gist.github.com/michalmarczyk/5991353). –

5

Twoje podejście to dość normalny kod Clojure. Jeden inny wybór jest użycie obietnic lub jeśli potrzebujesz bardziej złożonego przetwarzania można rozważyć użycie coś jak lamina lub jeśli czujesz, jak życie na krawędzi krwawienia you could try core.async:

(ns async-example.core 
    (:require [clojure.core.async :refer :all]) 

(defn example [] 
    (let [a (chan) ; a channel for a to report it's answer 
     b (chan) ; a channel for b to report it's answer 
     output (chan)] ; a channel for the reporter to report back to the repl 
    (go (<! (timeout (rand-int 1000))) ; process a 
     (>! a (rand-nth [true false]))) 
    (go (<! (timeout (rand-int 1000))) ; process b 
     (>! b (rand-nth [true false]))) 
    (go (>! output (and (<! a) (<! b)))) ; the reporter process 
    output)) ;return the channe that the result will be sent to 

async-example.core> (<!! (go (<! (example)))) 
false 
async-example.core> (<!! (go (<! (example)))) 
false 
async-example.core> (<!! (go (<! (example)))) 
true 

Oczywiście to przesada do sytuacji, choć to ogromnie zabawa i tak ;-)

+0

Używanie 'Thread/sleep' w core.async code jest w pewnym sensie złem - pochłania pulę wątków śpiących. Natywna metoda core.async to zrobić z 'core.async/timeout', która zapali stan bloku na czas trwania. –

+0

tak, to absolutnie poprawne, zmieniłem kod, aby używać limitu czasu. Dzięki za wskazanie, że –