İş parçacığı güvenliği, Java ve C # tarafından bellek güvenliğinin sağlanmasına benzer bir programlama dili tarafından nasıl sağlanabilir?


10

Java ve C #, dizi sınırlarını ve işaretçi referanslarını kontrol ederek bellek güvenliği sağlar.

Yarış koşulları ve kilitlenme olasılığını önlemek için bir programlama dilinde hangi mekanizmalar uygulanabilir?



2
Her şeyi değişmez hale getirin veya güvenli kanallarla her şeyi asenkron yapın. Ayrıca ilginizi çekebilir Go ve Erlang .
Theraot

@Theraot "güvenli kanallarla her şeyi zaman uyumsuz hale getir" - bununla ilgili ayrıntılı bilgi vermenizi dilerim.
mrpyo

2
mrpyo süreçleri veya konuları açığa çıkarmazsınız, her çağrı bir vaattir, her şey eşzamanlı olarak çalışır (çalışma zamanının yürütülmesini planlaması ve gerektiğinde sahnelerin arkasında sistem iş parçacıkları oluşturma / havuzlama) ve durumu koruyan mantık mekanizmalardadır bilgi aktarır ... çalışma zamanı, zamanlama ile otomatik olarak serileştirilebilir ve daha fazla nüans davranışı için, özellikle üretici / tüketici ve toplamalara ihtiyaç duyulan, iş parçacığı için güvenli bir çözüm içeren standart bir kütüphane olacaktır.
Theraot

2
Bu arada, başka bir olası yaklaşım daha var: işlemsel bellek .
Theraot

Yanıtlar:


14

Irklar, bir nesnenin aynı anda takma adı varsa ve en azından takma adlardan biri mutasyona uğradığında ortaya çıkar.

Dolayısıyla, yarışları önlemek için, bu koşullardan bir veya daha fazlasını yanlış yapmanız gerekir.

Çeşitli yaklaşımlar çeşitli yönleri ele alır. Fonksiyonel programlama, değişmezliği ortadan kaldıran değişmezliği vurgular. Kilitleme / atomlar eşzamanlılığı ortadan kaldırır. Afin tipler takma adı kaldırır (Rust değiştirilebilir takma adı kaldırır). Aktör modelleri genellikle takma adı kaldırır.

Yukarıdaki koşullardan kaçınılmasını sağlamak daha kolay olacak şekilde, diğer adlara eklenebilecek nesneleri kısıtlayabilirsiniz. Burada kanallar ve / veya mesaj aktarma stilleri devreye giriyor. Genellikle kilitler veya atomlar gibi eşzamanlılıktan kaçınarak.

Bu çeşitli mekanizmaların dezavantajı, yazabileceğiniz programları kısıtlamalarıdır. Kısıtlama ne kadar kör olursa, programlar o kadar az olur. Yani hiçbir takma ad veya değişiklik yapılamaz ve bununla ilgili akıl yürütmek kolaydır, ancak çok sınırlayıcıdır.

Bu yüzden Rust böyle bir harekete neden oluyor. Bu, takma adı ve değişebilirliği destekleyen ancak derleyicinin eşzamanlı olarak gerçekleşmediğini kontrol etmesini sağlayan bir mühendislik dilidir (akademik dilde olduğu gibi). İdeal olmasa da, daha büyük bir program sınıfının öncüllerinden daha güvenli bir şekilde yazılmasına izin verir.


11

Java ve C #, dizi sınırlarını ve işaretçi referanslarını kontrol ederek bellek güvenliği sağlar.

Önce C # ve Java'nın bunu nasıl yaptığını düşünmek önemlidir. Onlar ne dönüştürerek bunu tanımlanmamış tanımlanan davranışa içine C veya C ++ davranışı: programı çökmesine . Boş dereferences ve dizi dizini istisnaları hiçbir zaman doğru bir C # veya Java programında yakalanmamalıdır ; ilk etapta gerçekleşmemeliler çünkü program bu hataya sahip olmamalı.

Ama bence sorunuzla ne demek istediğinizi değil! Birbirini karşılıklı olarak bekleyen n iş parçacığı olup olmadığını periyodik olarak kontrol eden ve bu durumda programı sonlandıran bir "kilitlenme güvenli" çalışma zamanı yazabiliriz, ancak bunun sizi tatmin edeceğini düşünmüyorum.

Yarış koşulları ve kilitlenme olasılığını önlemek için bir programlama dilinde hangi mekanizmalar uygulanabilir?

Sorunuzla karşılaştığımız bir sonraki sorun, kilitlenmelerin aksine "yarış koşullarının" tespit edilmesinin zor olduğudur. Unutmayın, iplik güvenliğinde peşinde olduğumuz şey ırkları ortadan kaldırmak değildir . Peşinde olduğumuz şey , yarışı kim kazanırsa kazansın programı düzeltmek ! Yarış koşulları ile ilgili sorun, iki iplik tanımsız bir sırada çalışıyor olması ve ilk önce kimin bitireceğini bilmiyoruz. Yarış koşullarındaki sorun, geliştiricilerin bazı iplik işleme siparişlerinin mümkün olduğunu ve bu olasılığı hesaba katmadıklarını unutmalarıdır.

Yani sorunuz temel olarak "bir programlama dilinin programımın doğru olduğundan emin olmasının bir yolu var mı?" ve bu sorunun cevabı pratikte hayır.

Şimdiye kadar sadece sorunuzu eleştirdim. Burada vites değiştirmeye çalışın ve sorunuzun ruhunu ele alalım. Dil tasarımcılarının, çoklu iş parçacığımızla yaşadığımız korkunç durumu azaltabilecek seçimleri var mı?

Durum gerçekten korkunç! Özellikle çok zayıf bellek modeli mimarilerinde çok iş parçacıklı kodların düzeltilmesi çok, çok zordur. Neden zor olduğunu düşünmek öğretici:

  • Bir süreçte birden fazla kontrol iş parçacığının akla gelmesi zordur. Bir iplik yeterince sert!
  • Çok iş parçacıklı bir dünyada soyutlamalar son derece sızdı. Tek iş parçacıklı dünyada, programların gerçekte düzgün çalışmasalar bile sırayla çalışıyormuş gibi davrandıklarını garanti ederiz. Çok iş parçacıklı dünyada artık durum böyle değil; tek bir iş parçacığında görünmeyen optimizasyonlar görünür hale gelir ve şimdi geliştiricinin bu olası optimizasyonları anlaması gerekir.
  • Ama daha da kötüleşiyor. C # belirtimi, bir uygulamanın tüm iş parçacıkları tarafından üzerinde anlaşılabilecek tutarlı bir okuma ve yazma sırasına sahip olması GEREKMEZ olduğunu belirtir . "Yarış" olduğu ve açık bir kazanan olduğu düşüncesi aslında doğru değil! Birçok iş parçacığında bazı değişkenlere iki yazma ve iki okuma bulunan bir durum düşünün. Mantıklı bir dünyada "yarışları kimin kazanacağını bilemeyiz, ama en azından bir yarış olacak ve birileri kazanacak" diye düşünebiliriz. Biz bu mantıklı dünyada değiliz. C #, birden çok iş parçacığının, okuma ve yazma işlemlerinin gerçekleştiği sıraya katılmamalarına izin verir ; herkesin gözlemlediği tutarlı bir dünya olması gerekmez.

Dolayısıyla, dil tasarımcılarının işleri daha iyi hale getirebileceği açık bir yol var. Modern işlemcilerin performans kazançlarını bırakın . Tüm programların, çok iş parçacıklı programların bile son derece güçlü bir bellek modeline sahip olmasını sağlayın. Bu, çok iş parçacıklı programları birçok kez daha yavaş hale getirir, bu da çok iş parçacıklı programların ilk etapta doğrudan çalışmasına neden olur: daha iyi performans için.

Bellek modelini bir kenara bırakırsak bile, çoklu iş parçacığının zor olmasının başka nedenleri vardır:

  • Kilitlenmeleri önlemek için tüm program analizi gerekir; kilitlerin alınabileceği küresel düzeni bilmeniz ve program farklı kuruluşlar tarafından farklı zamanlarda yazılmış bileşenlerden oluşsa bile, bu düzeni tüm program boyunca uygulamanız gerekir.
  • Çok iş parçacığını evcilleştirmek için size verdiğimiz birincil araç kilittir, ancak kilitler oluşturulamaz .

Bu son nokta daha fazla açıklama içermektedir. "Bileşilebilir" derken aşağıdakileri kastediyorum:

Diyelim ki bir çifte verilen bir int hesaplamak istiyoruz. Hesaplamanın doğru bir uygulamasını yazıyoruz:

int F(double x) { correct implementation here }

Bir int verildiğinde bir dizeyi hesaplamak istediğimizi varsayalım:

string G(int y) { correct implementation here }

Şimdi çift verilen bir dizeyi hesaplamak istiyorsak:

double d = whatever;
string r = G(F(d));

G ve F , daha karmaşık soruna doğru bir çözüm olarak oluşturulabilir .

Ancak kilitler, kilitlenmeler nedeniyle bu özelliğe sahip değildir. L1, L2 sırasına göre kilitleri alan doğru bir yöntem M1 ve L2, L1 sırasına göre kilitleri alan doğru bir M2 yöntemi yanlış bir program oluşturmadan aynı programda kullanılamaz. Kilitler, "her bireysel yöntem doğru, bu yüzden her şey doğru" diyemeyeceğiniz şekilde yapar.

Peki, dil tasarımcıları olarak ne yapabiliriz?

İlk olarak, oraya gitme. Bir programdaki birden fazla denetim iş parçacığı kötü bir fikirdir ve iş parçacıkları arasında bellek paylaşmak kötü bir fikirdir, bu yüzden onu ilk etapta dile veya çalışma zamanına koymayın.

Görünüşe göre bu bir başlangıç ​​değil.

O zaman dikkatimizi daha temel soruya çevirelim: neden ilk etapta birden fazla iş parçacığımız var? İki ana sebep vardır ve çok farklı olmalarına rağmen, aynı şeyle sık sık ilişkilidirler. Her ikisi de gecikmeyi yönetmekle ilgili olduğu için sınırlıdırlar.

  • G / Ç gecikmesini yönetmek için yanlış iş parçacıkları oluşturuyoruz. UI iş parçacığınızı kilitlemek yerine büyük bir dosya yazmanız, uzak bir veritabanına erişmeniz gerekiyor.

Kötü bir fikir. Bunun yerine, coroutines aracılığıyla tek iş parçacıklı asenkron kullanın. C # bunu güzel yapıyor. Java, pek iyi değil. Ancak , dil tasarımcılarının mevcut ürününün diş çekme problemini çözmeye yardımcı olmasının ana yolu budur. await(F # asenkron iş akışları ve diğer önceki teknik esinlenerek) C # operatör daha fazla dil içine dahil edilmektedir.

  • Boştaki CPU'ları hesaplamalı ağır işlerle doyurmak için doğru şekilde iplikler oluştururuz. Temel olarak, iplikleri hafif süreçler olarak kullanıyoruz.

Dil tasarımcıları, paralellikle iyi çalışan dil özellikleri oluşturarak yardımcı olabilir. Örneğin, LINQ'nun doğal olarak PLINQ'ya nasıl genişletildiğini düşünün. Mantıklı bir kişiyseniz ve TPL işlemlerinizi oldukça paralel olan ve belleği paylaşmayan CPU'ya bağlı işlemlerle sınırlandırırsanız, burada büyük kazançlar elde edebilirsiniz.

Başka ne yapabiliriz?

  • Derleyicinin en çok okunan hataları algılamasını sağlayın ve uyarılara veya hatalara dönüştürün.

C #, kilitlenmeyi beklemenize izin vermez, çünkü bu kilitlenmeler için bir reçetedir. C # değer türünü kilitlemenize izin vermez, çünkü bu her zaman yanlış bir şeydir; kutuyu kilitliyorsunuz, değeri değil. C #, bir diğer adı geçici olarak kullanırsanız sizi uyarır, çünkü diğer ad alma / bırakma anlambilimi uygulamaz. Derleyicinin sık karşılaşılan sorunları saptamasının ve önlemesinin birçok yolu vardır.

  • Bunu yapmanın en doğal yolunun da en doğru yol olduğu "kalite çukuru" özellikleri tasarlayın.

C # ve Java, herhangi bir referans nesnesini monitör olarak kullanmanıza izin vererek büyük bir tasarım hatası yaptı. Bu, kilitlenmeleri izlemeyi zorlaştıran ve statik olarak önlemeyi zorlaştıran her türlü kötü uygulamayı teşvik eder. Ve her nesne başlığında bayt israf eder. Monitörlerin bir monitör sınıfından türetilmesi gerekir.

  • Büyük miktarda Microsoft Research zaman ve çabası, C # benzeri bir dile yazılım işlem belleği ekleme girişimine girdi ve bunu ana dile dahil edecek kadar iyi performans göstermediler.

STM güzel bir fikir ve Haskell'de oyuncak uygulamaları ile oynadım; kilit tabanlı çözümlerden çok doğru parçaları doğru çözümlerden daha şık bir şekilde oluşturmanıza olanak tanır. Ancak, neden ölçekli çalışmanın yapılamadığını söyleyecek ayrıntılar hakkında yeterli bilgim yok; Joe Duffy'e bir sonraki görüşünde sor.

  • Başka bir cevap değişmezlikten daha önce bahsetti. Etkili koroutinlerle birlikte değişmezliğiniz varsa, aktör modeli gibi özellikleri doğrudan kendi dilinizde oluşturabilirsiniz; örneğin Erlang'ı düşünün.

Analiz temelli dilleri işlemek için çok fazla araştırma yapıldı ve bu alanı çok iyi anlamıyorum; üzerinde birkaç makale okumaya çalışın ve herhangi bir öngörü alıp almadığınızı görün.

  • Üçüncü tarafların iyi analizörler yazmasını kolaylaştırın

Roslyn'de Microsoft'ta çalıştıktan sonra Coverity'de çalıştım ve yaptığım şeylerden biri Roslyn'i kullanarak analizörün ön ucunu almaktı. Microsoft tarafından sağlanan doğru bir sözcüksel, sözdizimsel ve anlambilimsel çözümleme yaparak, ortak çok iş parçacıklı sorunlar bulan dedektör yazma çalışmalarına yoğunlaşabiliriz.

  • Soyutlama seviyesini yükseltin

Irklar, çıkmazlar ve tüm bu şeylere sahip olmamızın temel bir nedeni , ne yapacağımızı söyleyen programlar yazmamızdır ve hepimizin zorunlu programlar yazarken saçmalık olduğumuzdur; bilgisayar söylediklerinizi yapar ve biz de yanlış şeyleri yapmasını söyleriz. Birçok modern programlama dili, bildirimsel programlama hakkında gittikçe daha fazladır: istediğiniz sonuçları söyleyin ve derleyicinin bu sonuca ulaşmak için etkili, güvenli ve doğru yolu bulmasına izin verin. Yine, LINQ düşünün; söylemenizi istiyoruz from c in customers select c.FirstName, ki bu bir niyeti ifade ediyor . Derleyicinin kodu nasıl yazacağını bulmasına izin verin.

  • Bilgisayar sorunlarını çözmek için bilgisayar kullanın.

Makine öğrenme algoritmaları bazı görevlerde elle kodlanmış algoritmalardan çok daha iyidir, ancak elbette doğruluk, eğitim için harcanan zaman, kötü eğitimin getirdiği önyargılar vb. Ancak şu anda "elle" kodladığımız pek çok görevin yakında makine tarafından üretilen çözümlere uygun olması muhtemeldir. İnsanlar kodu yazmıyorlarsa, hata yazmıyorlar.

Üzgünüm, orada biraz dolaşıyordu; bu çok büyük ve zor bir konu ve bu sorun alanındaki ilerlemeyi takip ettiğim 20 yıldır PL topluluğunda net bir fikir birliği oluşmadı.


"Yani sorunuz temel olarak" bir programlama dilinin programımın doğru olduğundan emin olmasının bir yolu var mı? "Ve bu sorunun cevabı pratikte hayır." - aslında, oldukça mümkün - buna resmi doğrulama denir ve sakıncalı olsa da, kritik yazılım üzerinde rutin olarak yapıldığından eminim, bu yüzden pratik olarak adlandırmam. Ama sen bir dil tasarımcısı olman muhtemelen bunu biliyor ...
mrpyo

6
@mrpyo: Çok farkındayım. Birçok sorun var. Birincisi: Bir keresinde MSFT araştırma ekibinin heyecan verici yeni bir sonuç sunduğu resmi bir doğrulama konferansına katıldım: yirmi satır uzunluğundaki çok iş parçacıklı programları doğrulamak ve doğrulayıcıyı bir haftadan daha kısa sürede çalıştırmak için tekniklerini genişletebildiler . Bu ilginç bir sunumdu ama benim için bir yararı yoktu; Analiz etmek için 20 milyon hat programım vardı.
Eric Lippert

@mrpyo: İkinci olarak, belirttiğim gibi, kilitlerle ilgili büyük bir sorun, iş parçacığı güvenli yöntemlerden oluşan bir programın her zaman iş parçacığı güvenli bir program olmamasıdır. Tek tek yöntemleri resmi olarak doğrulamak mutlaka yardımcı olmaz ve tüm program analizi önemsiz programlar için zordur.
Eric Lippert

6
@mrpyo: Üçüncüsü, resmi analizle ilgili en büyük sorun, temelde ne yapıyoruz? Bir ön koşul ve son koşul belirtimi sunuyoruz ve ardından programın bu belirtime uygun olduğunu doğrulıyoruz. Harika; teoride tamamen yapılabilir. Spesifikasyon hangi dilde yazılmıştır? Bir kesin, doğrulanabilir şartname dil varsa o zaman sadece tüm programlar yazalım o dil ve derlemek o . Bunu neden yapmıyoruz? Çünkü spesifik dilde doğru programlar yazmak gerçekten zor!
Eric Lippert

2
Bir başvurunun, ön koşullar / son koşullar kullanılarak doğruluk açısından analiz edilmesi mümkündür (örn. Kodlama Sözleşmeleri kullanılarak). Bununla birlikte, bu analiz sadece koşulların oluşturulabilir olması koşuluyla mümkündür, ki bu kilitler değildir. Ayrıca analize izin verecek şekilde bir program yazmanın dikkatli bir disiplin gerektirdiğini de not edeceğim. Örneğin, Liskov İkame Prensibi'ne sıkı sıkıya bağlı olmayan uygulamalar analize direnme eğilimindedir.
Brian
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.