Ç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 += 1
iş parçacığı güvenli değildir, çünkü çoklu işlemlerdir. @n = 1
iş 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 + b
tamam, a = b
aynı zamanda tamam ve a.foo(b)
sorun yok, eğer yöntem foo
yan 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; end
olduğ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 stuff
eriş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_stuff
ilk kullanıldığında iyi çalışır, ancak ikinci seferde başka bir şey döndürür. Neden? load_things
Yöntem kendisine geçirilen seçenekleri karma sahibi ve yapar düşünmek olur color = options.delete(:color)
. Artık STANDARD_OPTIONS
sabit 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.