Yönettiğim yeni ekibimde kodumuzun çoğunluğu platform, TCP soketi ve http ağ kodu. Hepsi C ++. Bunların çoğu takımı terk eden diğer geliştiricilere dayanıyor. Takımdaki mevcut geliştiriciler çok zeki, ancak çoğunlukla deneyim açısından küçük.
En büyük sorunumuz: Çok iş parçacıklı eşzamanlılık hataları. Sınıf kütüphanelerimizin çoğu bazı thread pool sınıfları kullanılarak asenkronize olarak yazılmıştır. Sınıf kitaplıkları üzerindeki yöntemler, genellikle bir havuzdaki iş parçacığı havuzunda uzun süreli alımları sıkar ve daha sonra bu sınıfın geri çağırma yöntemleri farklı bir iş parçacığına çağrılır. Sonuç olarak, yanlış diş açma varsayımlarını içeren çok sayıda ileri düzeyde hatamız var. Bu, eşzamanlılık sorunlarına karşı korunmak için yalnızca kritik bölümlerin ve kilitlerin ötesine geçen ince hatalara neden olur.
Bu problemleri daha da zorlaştıran şey, tamir etme girişimlerinin çoğu zaman yanlış olmasıdır. Bazı denemeler yapan takımın (veya eski kodun kendisinde) gözlemlediğim bazı hatalar aşağıdaki gibi bir şey içeriyor:
Yaygın hata # 1 - Eşzamanlılık sorununu yalnızca paylaşılan verilerin çevresine kilitleyerek düzeltmek, ancak yöntemler beklenen bir sırada çağrılmadığında ne olacağını unutmak. İşte çok basit bir örnek:
void Foo::OnHttpRequestComplete(statuscode status)
{
m_pBar->DoSomethingImportant(status);
}
void Foo::Shutdown()
{
m_pBar->Cleanup();
delete m_pBar;
m_pBar=nullptr;
}
Şimdi, OnHttpNetworkRequestComplete gerçekleşirken Shutdown'ın çağrılabileceği bir hatamız var. Bir test cihazı hatayı bulur, kilitlenme çöplüğünü yakalar ve hatayı bir geliştiriciye atar. O da böceği böceği böyle düzeltir.
void Foo::OnHttpRequestComplete(statuscode status)
{
AutoLock lock(m_cs);
m_pBar->DoSomethingImportant(status);
}
void Foo::Shutdown()
{
AutoLock lock(m_cs);
m_pBar->Cleanup();
delete m_pBar;
m_pBar=nullptr;
}
Yukarıdaki düzeltme daha da ince bir dava olduğunu anlayana kadar iyi görünüyor. OnHttpRequestComplete geri çağrılmadan önce Kapatma çağrılırsa ne olur ? Ekibimin sahip olduğu gerçek dünya örnekleri daha da karmaşık ve kod incelemesi sırasında son durumları tespit etmek daha da zor.
Yaygın Hata # 2 - kilitlenmeden kilitlenmeden çıkma kilitlenme sorunlarını gidermek, diğer iş parçacığının bitmesini bekleyin, sonra kilidi tekrar girin - ancak nesnenin diğer iş parçacığı tarafından henüz güncellendiği durumuyla ilgilenmeden!
Genel Hata # 3 - Nesneler referans sayılsa bile, kapatma dizisi göstericiyi "serbest bırakır". Ancak, örneğini serbest bırakmak için hala çalışan iş parçacığını beklemeyi unutur. Bu nedenle, bileşenler temiz bir şekilde kapatılır, ardından daha fazla arama beklemeyen bir nesnede sahte veya geç geri aramalar başlatılır.
Başka kenar davaları da var, ama sonuç şu:
Çok iş parçacıklı programlama, akıllı insanlar için bile oldukça kolaydır.
Bu hataları yakaladığımda, her geliştiriciyle daha uygun bir düzeltme geliştirmek için hataları tartışarak zaman harcıyorum. Ancak, “doğru” düzeltmenin dokunmayı içereceği muazzam miktarda eski kod nedeniyle her konunun nasıl çözüleceği konusunda sık sık karıştığından şüpheleniyorum.
Yakında satışa çıkacağız ve eminim uyguladığımız yamalar gelecek sürüm için geçerli olacak. Daha sonra, gerektiğinde kod tabanını ve yeniden düzenleyiciyi geliştirmek için biraz zamanımız olacak. Her şeyi yeniden yazmak için zamanımız olmayacak. Ve kodun çoğunluğu o kadar da kötü değil. Ancak, diş açma sorunlarından tamamen kaçınılabilecek şekilde yeniden kodlama kodunu arıyorum.
Düşündüğüm tek yaklaşım bu. Her önemli platform özelliği için, tüm olayların ve ağ geri aramalarının birleştirildiği tek bir iş parçacığına sahip olun. Bir mesaj döngüsünün kullanımıyla Windows'taki COM dairesine benzer. Uzun engelleme işlemleri hala bir iş havuzu iş parçacığına gönderilebilir, ancak tamamlama geri çağrısı bileşen iş parçacığında çağrılır. Bileşenler muhtemelen aynı dişi bile paylaşabilirdi. Daha sonra ipliğin içinde çalışan tüm sınıf kütüphaneleri, tek bir dişli dünya varsayımı altında yazılabilir.
Bu yolda ilerlemeden önce, çok iş parçacıklı konularla ilgilenmek için başka standart teknikler veya tasarım desenleri olup olmadığını da çok ilgileniyorum. Ve vurgulamalıyım - muteksilerin ve semaforların temellerini tanımlayan kitabın ötesinde bir şey. Ne düşünüyorsun?
Ayrıca bir yeniden düzenleme işlemine yönelik diğer yaklaşımlarla da ilgileniyorum. Aşağıdakilerden herhangi birini içerenler:
Konu etrafındaki tasarım desenleri üzerine literatür veya yazılar. Mutekslere ve semaforlara girişin ötesinde bir şey. Eşzamanlı paralelliğe de ihtiyacımız yok, sadece diğer konuların eşzamansız olaylarını doğru bir şekilde ele almak için bir nesne modeli tasarlamanın yolları .
Çeşitli bileşenlerin iş parçacığını şemalandırmanın yolları; böylece çözüm ve çözümlerini geliştirmek kolay olacaktır. (Yani, nesneler ve sınıflar arasında iş parçacıklarını tartışmak için bir UML'ye eşdeğerdir)
Geliştirme ekibinizi çok iş parçacıklı kod içeren konular hakkında eğitin.
Sen ne yapardın?