Software Transactional Memory ve .NET

Software Transactional Memory (STM), veritabanlarında bulunan transaction yapısının avantajlarını kullanarak kodun herhangi bir parçasında transaction bütünlüğünü sağlayabileceğimiz bir mekanizma. STM'yi direkt olarak kullanabildiğimiz diller (Clojure) olabileceği gibi, Java, C# gibi dillerde de farklı kütüphaneler ile STM desteğini sağlayabiliyoruz. Örnek kodlarda, bulabildiğim en güncel C# STM kütüphanesi olan Shielded'ı kullandım.

Mesela elimizde aşağıdaki gibi bir kod olduğunu düşünelim;

Kod aslında counter 'ı 2 thread kullanarak \(2 \times 10^8\) yapmaya çalışıyor ama thread-safe olmadığı için her çalıştırmada rastgele ve beklenen değerden küçük sonuçlar verecek. Bunu düzeltmek için C#'ın bize sağladığı lock  yapısını kullanıyoruz.

Bu kod bize her seferinde doğru sonucu verecek. Basit bir durum olduğu için lock bloğunu rahatça kullandık. Peki eğer elimizde 2 counter varsa ne yapacağız? Yeni durumda counter1  eskisi gibi çalışsın, buna ek olarak da counter1'in 100'ün katını aldığı her değerde counter2'yi 1 arttıralım. 3. bir thread'le de counter2'yi teker teker arttıralım. Bu durumda kodu nasıl thread-safe yapacağız? Bir örneği aşağıdaki gibi;

Kodu daha basit yapmak için tüm işlemlerde mutex1 'i kullanabilirim ama bu doğru değil, counter2 'yi de bağımsız olarak arttırabilmem lazım. Bir counter daha ekleyince kod gereksiz yere karmaşıklaştı.

Burada alternatif olarak STM'yi deneyebilirim artık.

Son kod ile iç içe bir sürü lock kullanmadan işi hallettim.

Shielded bu kontrolleri dilin verdiği lock yapısı ile değil, nesnelere birer zaman etiketi atayarak ve her erişimde bunu kontrol ederek yapıyor. Nesnenin en son haline erişemediğimiz halde de verdiğimiz kod bloğunu baştan çalıştırıyor. Bu tekrar durumunu göz önünde bulundurarak, blok içindeki Shielded<>  olmayan nesnelere ve I/O gibi transaction dışı işlemlere güvenmemek gerekiyor. Yani, run2 'deki bloğun başına bir Console.WriteLine  yazarsak, her çalıştırmada konsola yazılan satır sayısı aynı olmayabilir.

Son olarak, Shielded kütüphanesi içinde Tree, HashSet, Dictionary gibi yapıların transaction ile çalışan hallerini bulmak da mümkün.

"Dostlar Alışverişte Görsün" Design Pattern

Diyelim ki bir işi framework kullanıcılarına sunuyoruz. Bu iş de mesela, byte matrisini alıp belirli resim formatlarına çevirmek olsun (örneği sallıyorum tamamen). Bu DLL'i kullanacak insanları düşünerek, kodun da daha 2. kod geliştirmede çöpe dönmesini istemeyen insanlar olarak basitçe aşağıdaki gibi bir yapı veriyoruz;

Sonrasında da istediğimiz kadar formatı bu interface'ten türetebiliriz

Artık bunları da factory mi kullanırsın, ne yaparsın oradan türeterek insanlara dönersin, insanlar da interface üzerinden kullanır değil mi?

Ne güzel mis gibi oldu değil mi? Ha AbstractImageEncoder  falan da eklersin de genel yaklaşımın bu olması gerekiyor en azından.

Peki Türk yazılım standartlarına göre bu sorunu nasıl çözüyoruz? Diyelim ki sizden önceki yazılımcı (ben) bu interface'i yazmış ve örnek olacak şekilde bir alt sınıfını da yazmış. Siz buna uyuyor musunuz? Tabii ki hayır! Önce gidip, bu işi yapan eşşek kadar DLL olmasına rağmen yeni format için yeni bir DLL oluşturuyor, aşağıdaki gibi kendi kodunuzu yazıyorsunuz.

Sonra insan gibi sizi uyarıyoruz, diyoruz ki arkadaş zaten bununla ilgili bir yapı var, kodunu buna entegre et. Sen napıyorsun sonra güzel kardeşim;

...

Üniversitelerde API tasarımı diye bi ders olması lazım yahu.