Ruby'de neyin güvenli OLMADIĞINI nasıl bilebilirim?


93

Rails 4'ten başlayarak , her şeyin varsayılan olarak iş parçacıklı ortamda çalışması gerekir. Bunun anlamı, yazdığımız kodun tamamı VE kullandığımız TÜM mücevherlerinthreadsafe

bu yüzden bununla ilgili birkaç sorum var:

  1. yakut / raylarda güvenli olmayan nedir? Vs Ruby / raylarda güvenli olan nedir?
  2. İş parçacığı açısından güvenli veya tersi olduğu bilinen mücevherlerin bir listesi var mı ?
  3. İş parçacığı için güvenli olmayan ortak kod kalıplarının listesi var @result ||= some_methodmı?
  4. Ruby lang çekirdeğindeki veri yapıları Hashvs thread güvenli midir?
  5. MRI'da, bir GVL/GIL yani bir seferde yalnızca 1 yakut ipliğin çalışabileceği anlamına gelir, ancak ipliğin güvenli bir şekilde IOdeğiştirilmesi bizi etkiler mi?

2
Tüm kodların ve tüm mücevherlerin iş parçacığı için güvenli OLMASI gerektiğinden emin misiniz? Sürüm notlarının söylediği şey, Rails'in kendisinin iş
parçacığı güvenli

Çok iş parçacıklı testler, olası en kötü iş parçacığı güvenliği riski olacaktır. Bir ortam değişkeninin değerini test durumunuzda değiştirmeniz gerektiğinde, anında iş parçacığı güvenli değilsiniz. Bunun etrafında nasıl çalışırsın? Ve evet, tüm mücevherler diş güvenli olmalıdır.
Lukas Oberhuber

Yanıtlar:


110

Çekirdek veri yapılarının hiçbiri iş parçacığı açısından güvenli değildir. Ruby ile birlikte geldiğini bildiğim tek şey, standart kitaplıktaki ( require 'thread'; q = Queue.new) kuyruk uygulamasıdır .

MRI'ın GIL'i bizi iplik güvenliği sorunlarından kurtarmaz. Yalnızca iki iş parçacığının aynı anda Ruby kodunu çalıştıramamasını sağlar , yani aynı anda iki farklı CPU üzerinde. İleti dizileri, kodunuzun herhangi bir noktasında yine de duraklatılabilir ve devam ettirilebilir. @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }Örneğin, birden çok iş parçacığından paylaşılan bir değişkeni mutasyona uğratmak gibi kod yazarsanız , daha sonra paylaşılan değişkenin değeri deterministik değildir. GIL aşağı yukarı tek çekirdekli bir sistemin simülasyonudur, doğru eşzamanlı programlar yazmanın temel konularını değiştirmez.

MRI, Node.js gibi tek iş parçacıklı olsa bile, yine de eşzamanlılığı düşünmeniz gerekir. Arttırılmış değişkenli örnek iyi çalışacaktır, ancak yine de olayların deterministik olmayan sırayla gerçekleştiği ve bir geri çağırmanın diğerinin sonucunu bozduğu yarış koşullarını elde edebilirsiniz. Tek iş parçacıklı eşzamansız sistemler hakkında akıl yürütmek daha kolaydır, ancak eşzamanlılık sorunlarından muaf değildirler. Birden fazla kullanıcısı olan bir uygulamayı düşünün: İki kullanıcı bir Stack Overflow gönderisindeki düzenlemeye aşağı yukarı aynı anda basarsa, gönderiyi düzenlemek için biraz zaman harcayın ve ardından değişiklikleri daha sonra üçüncü bir kullanıcı tarafından görülecek aynı gönderiyi okudunuz mu?

Ruby'de, diğer eşzamanlı çalışma zamanlarının çoğunda olduğu gibi, birden fazla işlem olan hiçbir şey iş parçacığı açısından güvenli değildir. @n += 1iş parçacığı güvenli değildir, çünkü çoklu işlemlerdir. @n = 1iş parçacığı güvenlidir çünkü tek bir işlemdir (başlık altında çok sayıda işlem vardır ve neden "iş parçacığı güvenli" olduğunu ayrıntılı olarak açıklamaya çalışırsam muhtemelen başım belaya girer, ancak sonunda ödevlerden tutarsız sonuçlar almayacaksınız ). @n ||= 1, değildir ve başka hiçbir kısaltma işlemi + atama da değildir. Pek çok kez yaptığım bir hata yazmaktır return unless @started; @started = true, ki bu hiç de güvenli değildir.

Ruby için herhangi bir iş parçacığı güvenli ve iş parçacığı olmayan güvenli deyimler listesi bilmiyorum, ancak basit bir kural var: bir ifade yalnızca bir (yan etkisiz) işlem yapıyorsa, muhtemelen iş parçacığı açısından güvenlidir. Örneğin: a + btamam, a = baynı zamanda tamam ve a.foo(b)sorun yok, eğer yöntem fooyan etkisizse (Ruby'deki hemen hemen her şey bir yöntem çağrısı, hatta çoğu durumda atama olduğu için, bu diğer örnekler için de geçerlidir). Bu bağlamda yan etkiler, durumu değiştiren şeyler anlamına gelir. def foo(x); @x = x; endolduğu değil yan etkisi ücretsiz.

Ruby'de iş parçacığı güvenli kod yazmanın en zor yanlarından biri dizi, karma ve dizge dahil tüm temel veri yapılarının değiştirilebilir olmasıdır. Durumunuzun bir parçasını yanlışlıkla sızdırmak çok kolaydır ve bu parça değiştirilebilir olduğunda işler gerçekten alt üst olabilir. Aşağıdaki kodu göz önünde bulundurun:

class Thing
  attr_reader :stuff

  def initialize(initial_stuff)
    @stuff = initial_stuff
    @state_lock = Mutex.new
  end

  def add(item)
    @state_lock.synchronize do
      @stuff << item
    end
  end
end

Bu sınıfın bir örneği iş parçacıkları arasında paylaşılabilir ve ona güvenli bir şekilde bir şeyler ekleyebilirler, ancak bir eşzamanlılık hatası vardır (tek sorun bu değildir): nesnenin dahili durumu stufferişimci aracılığıyla sızar . Kapsülleme açısından sorunlu olmasının yanı sıra, bir kutu eşzamanlılık solucanı da açar. Belki birisi o diziyi alıp başka bir yere aktarır ve bu kod da sırayla dizinin artık o diziye sahip olduğunu ve onunla istediği her şeyi yapabileceğini düşünür.

Başka bir klasik Ruby örneği şudur:

STANDARD_OPTIONS = {:color => 'red', :count => 10}

def find_stuff
  @some_service.load_things('stuff', STANDARD_OPTIONS)
end

find_stuffilk kullanıldığında iyi çalışır, ancak ikinci seferde başka bir şey döndürür. Neden? load_thingsYöntem kendisine geçirilen seçenekleri karma sahibi ve yapar düşünmek olur color = options.delete(:color). Artık STANDARD_OPTIONSsabit artık aynı değere sahip değil. Sabitler yalnızca referans verdiklerinde sabittir, başvurdukları veri yapılarının sabitliğini garanti etmezler. Bu kod aynı anda çalıştırılırsa ne olacağını bir düşünün.

Paylaşılan değişken durumdan (örneğin, birden çok iş parçacığı tarafından erişilen nesnelerdeki örnek değişkenleri, birden çok iş parçacığı tarafından erişilen karma ve diziler gibi veri yapıları) kaçınmanız durumunda iş parçacığı güvenliği o kadar zor değildir. Uygulamanızın aynı anda erişilen kısımlarını küçültmeye çalışın ve çabalarınızı oraya odaklayın. IIRC, bir Rails uygulamasında, her istek için yeni bir denetleyici nesnesi oluşturulur, bu nedenle yalnızca tek bir iş parçacığı tarafından kullanılır ve bu denetleyiciden oluşturduğunuz tüm model nesneler için de geçerlidir. Bununla birlikte, Rails aynı zamanda global değişkenlerin kullanımını teşvik eder (global değişkeni User.find(...)kullanırUser, bunu sadece bir sınıf olarak düşünebilirsiniz ve bir sınıftır, ancak aynı zamanda global değişkenler için bir isim alanıdır), bunlardan bazıları güvenlidir çünkü sadece okunurlar, ancak bazen bu global değişkenlere bir şeyler kaydedersiniz çünkü uygundur. Küresel olarak erişilebilen herhangi bir şeyi kullanırken çok dikkatli olun.

Bir süredir Rails'i iş parçacıklı ortamlarda çalıştırmak mümkündü, bu nedenle bir Rails uzmanı olmadan, Rails söz konusu olduğunda iş parçacığı güvenliği konusunda endişelenmenize gerek olmadığını söyleyecek kadar ileri gidebilirim. Yukarıda bahsettiğim bazı şeyleri yaparak hala güvenli olmayan Rails uygulamaları oluşturabilirsiniz. Konu geldiğinde, diğer mücevherler, öyle olduklarını söylemedikleri sürece iş parçacığı güvenli olmadıklarını varsayarlar ve olmadıklarını varsayarlarsa ve kodlarına bakarlar (ama sadece böyle şeyler yaptıklarını gördüğünüz için)@n ||= 1 iş parçacığı açısından güvenli olmadıkları anlamına gelmez, bu doğru bağlamda yapılması tamamen meşru bir şeydir - bunun yerine küresel değişkenlerde değişken durum gibi şeyleri, yöntemlerine aktarılan değişken nesneleri nasıl işlediğini ve özellikle nasıl seçenek karmalarını işler).

Son olarak, iş parçacığının güvenli olmaması geçişli bir özelliktir. İş parçacığı açısından güvenli olmayan bir şey kullanan herhangi bir şeyin kendisi iş parçacığı açısından güvenli değildir.


Mükemmel cevap. Tipik bir rails uygulamasının çok işlemli olduğunu göz önünde bulundurarak (açıkladığınız gibi, aynı uygulamaya erişen birçok farklı kullanıcı), eşzamanlılık modeline yönelik iş parçacıklarının marjinal riskinin ne olduğunu merak ediyorum ... Süreçler aracılığıyla zaten bir miktar eşzamanlılıkla uğraşıyorsanız, iş parçacıklı modda mı çalışacak?
gingerlime

2
@Theo Çok teşekkürler. Bu sürekli şey büyük bir bomba. İşlem güvenli bile değil. Sabit bir istekte değiştirilirse, sonraki isteklerin tek bir iş parçacığında bile değişen sabiti görmesine neden olur. Ruby sabitleri tuhaf
2013

5
STANDARD_OPTIONS = {...}.freezeSığ mutasyonları yükseltmek için yapın
glebm

Gerçekten harika cevap
Cheyne

3
" @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }[...] gibi bir kod yazarsanız , daha sonra paylaşılan değişkenin değeri deterministik değildir." - Bunun Ruby'nin sürümleri arasında farklılık gösterip göstermediğini biliyor musunuz? Örneğin, kodunuzu 1.8'de çalıştırmak farklı değerleri verir @n, ancak 1.9 ve sonrasında tutarlı @nolarak 300'e eşittir.
user200783

10

Theo'nun cevabına ek olarak, config.threadsafe'e geçiyorsanız, özellikle Rails'de aranacak birkaç sorun alanı ekleyeceğim!

  • Sınıf değişkenleri :

    @@i_exist_across_threads

  • ENV :

    ENV['DONT_CHANGE_ME']

  • Konular :

    Thread.start


9

Rails 4'ten başlayarak, her şeyin varsayılan olarak iş parçacıklı ortamda çalışması gerekir

Bu% 100 doğru değil. İş Parçacığı Güvenli Rails, varsayılan olarak yalnızca açıktır. Passenger (topluluk) veya Unicorn gibi çok işlemli bir uygulama sunucusuna dağıtırsanız hiçbir fark olmayacaktır. Bu değişiklik yalnızca, Puma veya Passenger Enterprise> 4.0 gibi çok iş parçacıklı bir ortama dağıtım yapıyorsanız sizi ilgilendirir.

Geçmişte, çok iş parçacıklı bir uygulama sunucusuna dağıtmak istiyorsanız , şimdi varsayılan olan config.threadsafe'i açmanız gerekiyordu , çünkü tüm yaptığı etkilere sahip değildi veya tek bir işlemde çalışan bir Rails uygulamasına da uygulandı ( Prooflink ).

Ancak, tüm Rails 4 akış avantajlarını ve çok iş parçacıklı dağıtımın diğer gerçek zamanlı öğelerini istiyorsanız, o zaman belki bu makaleyi ilginç bulacaksınız . @Theo üzücü olarak, bir Rails uygulaması için, aslında bir istek sırasında değişen statik durumu atlamanız gerekir. Bu takip edilmesi basit bir uygulama olsa da, bulduğunuz her mücevher için ne yazık ki bundan emin olamazsınız. Hatırladığım kadarıyla JRuby projesinden Charles Oliver Nutter bu podcast'te bununla ilgili bazı ipuçları verdi .

Ve eğer birden fazla evre tarafından erişilen bazı veri yapılarına ihtiyaç duyacağınız, saf bir eşzamanlı Ruby programlama yazmak istiyorsanız, belki thread_safe gem'i yararlı bulabilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.