2012-12-07 30 views
7

CUDA'nın __synchtreads() işlevinin Ruby'deki davranışını "çoğaltmaya" çalışıyorum. Özellikle, bazı kod yürütmesi gereken N iş parçacığı bir dizi var, daha sonra işlerinin geri kalanı ile devam etmeden önce hepsi yürütme orta noktasında birbirlerini beklemek. Örneğin:Ruby'de bir eşitleme engelinin uygulanması

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 } 

Bunu yapmak için hangi araçları kullanmam gerekiyor? Küresel bir karma kullanmayi denedim ve sonra tüm ileti dizileri kodun ilk kismi ile bittigini belirten bir bayrak ayarlayana kadar uyumaya çalistim. Düzgün çalışması için onu alamadım; Bu asılı ve kilitlenme ile sonuçlandı. Ben düşünMutex ve ConditionVariable'un bir kombinasyonunu kullanmam gerekiyor ama neden/nasıl olduğu konusunda emin değilim.

Düzeltme: 50 görünüm ve cevap yok! Bir ödül için bir aday gibi görünüyor ...

+0

@sawa Aslında, yukarıdaki kod gösterimde bulunan hatayı buldum ve işe başladım, ancak daha temiz önerilerde bulunacağım. '' Sleep() 'kötü uygulama mıdır? – user2398029

+0

'uyku 'iyi bir uygulama değildir. Kesinlikle kaçınmanız gereken bir şey değildir, ancak mümkün olduğunda kaçınmaya çalışın. Bir şekilde 'Thread # join' kullanabileceğinizi veya 'Fiber' kullanabileceğinizi hissediyorum. – sawa

+0

Teşekkürler, 'fiber'i bir etiket olarak ekleyeceğim. – user2398029

cevap

7

Eşitleme engelini uygulayalım. Ön tarafta tutulacak iş parçacığı sayısını, n, bilmek zorunda. İlk n - 1 çağrıları sırasında sync bariyeri çağıran bir iş parçacığının beklemesine neden olur. n numaralı telefon numarası tüm konuları uyandırır. sync arasında

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 

Tüm vücut yani, aynı anda iki parçacıkları tarafından yapılamaz, kritik bölümdür. Bu nedenle Mutex#synchronize numaralı çağrı.

@count'un azaltılmış değeri pozitif olduğunda, iplik dondurulur. Kilitlenmenin engellenmesi için muteksin bir argüman olarak ConditionVariable#wait aramasına geçilmesi kritik öneme sahiptir. İş parçacığını dondurmadan önce muteksin kilitlenmesini sağlar.

Basit bir deneme, 1k iş parçacığı başlatır ve bir diziye öğe eklemelerini sağlar. Öncelikle sıfırları eklerler, daha sonra senkronize eder ve eklerler. Beklenen sonuç, 1k’nın sıfır ve 1k’nın 2k elemanları ile sıralanmış dizisidir. Nitekim olarak

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? 

, yukarıda açıklanan tam olarak ne yapar ki a gem named barrier var.

Son bir notta, böyle durumlarda beklemek için uyku kullanmayın. ve is considered a bad practice bekleyen meşgul denir.

+0

Güzel cevap, çok yararlı! Bir soru: diziye eklerken senkronize etmek gerekli ('dizi << 0')? Kendi kilitlerini kullanan bir iplik güvenli dizi uygulaması kullanarak bunu aşmak mümkün olabilir mi? Yoksa bu çözümün çalışması için gerekli mi? – user2398029

+0

'mtx' Mutex, diziye yazılan iş parçacıkları arasında herhangi bir yarış koşulunun olmayacağını garanti etmek için buradadır. Tanımladığınız bir iş parçacığı dizisi de iyi çalışmalıdır. – Jan

0

İş parçacıklarının birbirlerini beklemesini sağlamakla ilgili önemli noktalar olabilir. Ama iş parçacığının gerçekten "orta nokta" da bitmesini sağlamak daha temiz olduğunu düşünüyorum, çünkü sorunuzun, dişlerin birbirlerinin "orta nokta" daki sonuçlara gereksinim duyduğu açıktır. Temiz tasarım çözümü, işlerinin sonucunu vermelerini sağlamak ve bunlara dayanan yepyeni bir dizi iplik başlatmalarıdır.

+0

% 100 katılıyorum, ancak bu bir CUDA öykünücüsü için olduğundan, bu amacı bozguna uğratıyor :) Kernelleri statik kod analizine göre bölebilirim, ama oraya gitmemi tercih ederim. – user2398029

+0

Bu durumda, daha önce söylediklerimin bir kelimesini değil, dikkat edin :-) –