2012-09-13 15 views
6

Ho due funzioni costose che sono indipendenti. Voglio eseguirli in parallelo. Non voglio trattare con il futuro e così via (sono nuovo di Clojure e facilmente confuso).Dead simple Fork-Join Join in Clojure

Sto cercando un modo semplice per eseguire contemporaneamente due funzioni. Voglio che funzioni come il seguente

(defn fn1 [input] ...) ; costly 
(defn fn2 [input] ...) ; costly 

(let [[out1 out2] (conc (fn1 x) (fn2 y))] ...) 

Voglio che questo restituisca un vettore con una coppia di uscite. Dovrebbe tornare solo una volta che entrambi i thread sono terminati. Idealmente dovrebbe funzionare per qualsiasi numero di input. Sospetto che questo sia un modello semplice.

+0

Quando dici che non vuoi avere a che fare con i futures, vuol dire che non vuoi che i futures vengano usati anche nella funzione "conc"? È idiomatico usare uno dei primitivi della concurrency Clojure in questo caso, per quanto ne so, anche se potrebbero essere nascosti da voi tramite l'incapsulamento in "conc". – JohnJ

+0

Verranno utilizzati alcuni primitivi di concorrenza. conc può essere sofisticato quanto vuoi. Semplicemente non voglio occuparmi di loro come utente. Sospetto che si tratti di "iniziare un futuro per ogni input", "attendere ogni output", "return". Forse dovrà essere una macro, non è sicuro. – MRocklin

+1

Definitivamente una macro se si desidera rinviare le valutazioni degli argomenti da conc ai thread. Sto lavorando alla macro def'n ora. – JohnJ

risposta

3

Avete bisogno di una macro per preservare la sintassi desiderata, sebbene ci siano altri modi per ottenere lo stesso comportamento, come indicano le altre risposte. Ecco un modo per farlo:

(defn f1 [x] (Thread/sleep 500) 5) 
(defn f2 [y] 2) 

(defmacro conc [& exprs] 
    `(map deref 
     [[email protected](for [x# exprs] `(future ~x#))])) 

(time (let [[a b] (conc (f1 6) (f2 7))] 
     [a b])) 
; "Elapsed time: 500.951 msecs" 
;= (5 2) 

L'espansione mostra come funziona:

(macroexpand-1 '(conc (f1 6) (f2 7))) 
;= (clojure.core/map clojure.core/deref [(clojure.core/future (f1 6)) 
;=          (clojure.core/future (f2 7))]) 

è stato specificato due funzioni, ma questo dovrebbe funzionare con qualsiasi numero di espressioni.

+0

Fantastico. Questo mi ha ispirato a provarlo da solo. Ho provato a farlo sostituendo il for con '(map future exprs)'. Purtroppo questo non ha funzionato perché il futuro è a sua volta una macro. Qualche idea su come questo potrebbe essere ulteriormente ridotto? – MRocklin

+0

Bel lavoro incluso il codice generato. – MRocklin

+1

Sì, questo è il problema con le macro - non è possibile comporle facilmente come funzioni. Non so come ridurlo ulteriormente, ma qualcuno nel [Gruppo Google] (https://groups.google.com/forum/?fromgroups#!forum/clojure) potrebbe. – JohnJ

4

L'utilizzo di future è molto semplice in Clojure. In ogni caso, qui è una risposta che li evita

(defn conc [& fns] 
    (doall (pmap (fn [f] (f)) fns))) 

pmap utilizza i futures sotto il cofano. doall imporrà la sequenza da valutare.

(let [[out1 out2] (conc fn1 fn2)] 
     [out1 out2]) 

nota, che ho destrutturato out1 e out2 nel tentativo di preservare il vostro esempio.

+0

per utilizzare questo è necessario avvolgere i tuoi ingressi in funzioni anonime cioè: # (fn1 42) –

+0

@ArthurUlfeldt corretto, nel qual caso vorrei andare con la soluzione di JohnJ. –

+0

e penso che non averlo avvolto in una macro porti a un codice componibile migliore. Le macro non sono cose di prim'ordine, non puoi passarle per mapparle o applicarle a liste di argomenti. –

2

ho capito che non vuoi la soluzione definitiva per esporre i futures anche se è utile per illustrare come fare questo con il futuro, e quindi avvolgerli in qualcosa che nasconde questo dettaglio:

core> (defn fn1 [input] (java.lang.Thread/sleep 2000) (inc input)) 
#'core/fn1                      
core> (defn fn2 [input] (java.lang.Thread/sleep 3000) (* 2 input)) 
#'core/fn2                      
core> (time (let [f1 (future (fn1 4)) f2 (future (fn2 4))] @f1 @f2)) 
"Elapsed time: 3000.791021 msecs" 

poi siamo può avvolgerlo in uno dei tanti involucri di clojure attorno ai futures. il più semplice è solo una funzione che prende due funzioni e le esegue in parallelo.

core> (defn conc [fn1 fn2] 
     (let [f1 (future (fn1)) 
       f2 (future (fn2))] [@f1 @f2])) 
#'core/conc                      
core> (time (conC#(fn1 4) #(fn2 4))) 
"Elapsed time: 3001.197634 msecs"                   

Questo evita la necessità di scrivere come una macro avente conc prendere la funzione per eseguire invece del corpo per valutare, e quindi creare le funzioni per passare ad esso mettendo # fronte delle chiamate.

Questo può anche essere scritto con la mappa e il futuro-call:

core> (map deref (map future-call [#(fn1 4) #(fn2 42)])) 
(5 84) 

È quindi possibile improce conc fino a che non assomiglia (come Julien Chastang sottolinea saggiamente fuori) pmap