2012-12-07 22 views
7

Sto provando a "replicare" il comportamento della funzione __synchtreads() di CUDA in Ruby. Nello specifico, ho un set di thread N che devono eseguire del codice, quindi tutti si aspettano l'un l'altro a metà del punto dell'esecuzione prima di continuare con il resto della loro attività. Ad esempio:Implementazione di una barriera di sincronizzazione in Ruby

x = 0 

a = Thread.new do 
    x = 1 
    syncthreads() 
end 

b = Thread.new do 
    syncthreads() 
    # x should have been changed 
    raise if x == 0 
end 

[a,b].each { |t| t.join } 

Quali strumenti sono necessari per eseguire questo? Ho provato a utilizzare un hash globale e quindi a dormire fino a quando tutti i thread hanno impostato un flag che indica che sono stati completati con la prima parte del codice. Non riuscivo a farlo funzionare correttamente; ha provocato blocchi e stallo. I penso Devo usare una combinazione di Mutex e ConditionVariable ma non sono sicuro del perché/come.

Modifica: 50 visualizzazioni e nessuna risposta! Sembra un candidato per una taglia ...

+0

@sawa In realtà ho trovato il bug nel mio codice mostrato sopra e l'ho fatto funzionare ma sono aperto a suggerimenti più chiari. 'Sleep()' è considerato una cattiva pratica? – user2398029

+0

'sleep' non è una buona pratica. Non è qualcosa da evitare assolutamente, ma cerca di evitare quando possibile. Ho la sensazione che tu possa in qualche modo utilizzare 'Thread # join 'o usare' Fiber'. – sawa

+0

Grazie, aggiungerò 'fiber' come tag. – user2398029

risposta

7

Implementiamo una barriera di sincronizzazione. È necessario conoscere il numero di thread che gestirà, n, in primo piano. Durante le prime chiamate n - 1 a sync la barriera causerà l'attesa di un thread di chiamata. Il numero di telefono n riattiverà tutti i thread.

class Barrier 
    def initialize(count) 
    @mutex = Mutex.new 
    @cond = ConditionVariable.new 
    @count = count 
    end 

    def sync 
    @mutex.synchronize do 
     @count -= 1 
     if @count > 0 
     @cond.wait @mutex 
     else 
     @cond.broadcast 
     end 
    end 
    end 
end 

intero corpo di sync è una sezione critica, cioè esso non può essere eseguita con due fili simultaneamente. Da qui la chiamata a Mutex#synchronize.

Quando il valore ridotto di @count è positivo, il thread è congelato. Passare il mutex come argomento alla chiamata a ConditionVariable#wait è fondamentale per evitare deadlock. Fa sì che il mutex sia sbloccato prima di congelare il filo.

Un semplice esperimento avvia i thread 1k e li fa aggiungere elementi a un array. In primo luogo aggiungono zeri, quindi si sincronizzano e ne aggiungono uno. Il risultato atteso è un array ordinato con 2k elementi, di cui 1k sono zeri e 1k sono uni.

mtx = Mutex.new 
arr = [] 
num = 1000 
barrier = Barrier.new num 
num.times.map do 
    Thread.start do 
    mtx.synchronize { arr << 0 } 
    barrier.sync 
    mtx.synchronize { arr << 1 } 
    end 
end .map &:join; 
# Prints true. See it break by deleting `barrier.sync`. 
puts [ 
    arr.sort == arr, 
    arr.count == 2 * num, 
    arr.count(&:zero?) == num, 
    arr.uniq == [0, 1], 
].all? 

È un dato di fatto, non c'è a gem named barrier che fa esattamente quello che ho descritto sopra.

In una nota finale, non utilizzare il sonno per l'attesa in tali circostanze. Si chiama occupato in attesa e is considered a bad practice.

+0

Bella risposta, molto utile! Una domanda: è necessario sincronizzare quando si aggiunge alla matrice ('array << 0')? Sarebbe possibile aggirare questo problema utilizzando un'implementazione dell'array thread-safe, che usa le proprie serrature? O è necessario che questa soluzione funzioni? – user2398029

+0

Il 'mtx' Mutex è qui per assicurare che non ci saranno condizioni di competizione tra i thread che scrivono sull'array. Anche un array thread-safe che stai descrivendo dovrebbe funzionare bene. – Jan

0

Potrebbe esserci il merito di avere i thread in attesa l'uno dell'altro. Ma penso che sia più pulito il fatto che i thread finiscano effettivamente in "punto medio", perché la tua domanda ovviamente non rende possibile che i thread abbiano bisogno dei reciproci risultati nel "punto medio". Una soluzione di design pulita sarebbe quella di lasciarli finire, consegnare il risultato del loro lavoro e iniziare un nuovo set di thread basato su questi.

+0

Sono d'accordo al 100%, ma dal momento che questo è per un emulatore CUDA, questo tipo di sconfigge lo scopo :) Potrei dividere i kernel basati sull'analisi del codice statico, ma preferirei non andare lì. – user2398029

+0

In tal caso, non prestare attenzione a ciò che ho detto prima :-) –

Problemi correlati