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?
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?
Yanıtlar:
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.
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:
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:
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.
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.
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?
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.
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.
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.
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.
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.
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.
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ı.