Thread Starvation Nedir ?
Özellikle web uygulamalarında yoğun kullanım olan zamanlarda sistemde yavaşlık yaşıyor ama sistemde uyumlu bir şekilde cpu veya network kullanımı görmüyorsanız bu yazı sizin için çözüm olabilir.
IIS veya benzeri tüm web sunucuları gelen istekleri karşılamak üzere aşağıdaki gibi bir gelen istekleri karşılamak üzere bir dinleyici thread ve asıl işin yapılmak üzere işin devredildiği içerisinde bir çok thread’in olduğu bir havuza devredilir. Bu havuza thread pool denilir.
Thread pool içerisinde kaç thread’in hazır bekleyeceği platform (IIS, dotnet core, java vb.) üzerinde belirlenir.
Thread starvation (thread kıtlığı) da işte bu havuz içerisindeki threadlerin yeterli olmadığı durumda karşılaşılan bir problemdir.
Neden ?
Önce neden bu problemin ortaya çıktığı ile başlayalım;
Örneğin, dotnet core platformunda uygulama başladığı zaman 4 adetle bir thread pool açılır. Gelen ilk 4 istek normal şekilde havuza alınır ancak 5. istek havuzda yer kalmadığı için beklemeye alınır ve ancak havuzdaki işlerden birisi bittiği zaman işleme alınır.
Asenkron yazılmış yazılımlarda bu kuyruğa alınma durumu sorun yaratmaz çünkü havuzdaki threadler birisi bekleme durumuna geçerse sıradaki diğer bekleyen işler havuza alınır.
Ancak senkron çalışan bir iş olduğu zaman zaman thread’i meşgul durumda tutarız ve senkron işlem tamamlana kadar başka bir işlem yapamaz. Örneğin backend tarafta bir servis çağrısının cevabının senkron şekilde bekleniyor olması thread pool’u doldurmak için yeterli olur.
Dotnet core içerisinde bu durumu engellemek için thread yetmediğinde otomatik olarak havuzu büyütecek bir mekanizma vardır ancak bu aşırı büyümeyi engellemek için havuza saniyede en fazla 1 thread ekleyecek şekilde çalışır.
Özellikle bu senkron çalışan işlem içerisinde yeni thread açılıyor ise durum daha kötü bir hale gelir. Sistem işlemleri tamamladıkça üzerindeki baskı daha da artar. Bu durumu anlamak için task manager içerisinden thread adedini kontrol etmek yeterlidir. Normalde 4–32 arasında sabit olması gereken thread adedinin sürekli olarak büyüdüğü görülür.
Bu limit nedeniyle bir anda çok fazla talep geldiğinde kullanıcılar yazılımın yavaş çalıştığını düşünür. Sisteme dışardan baktığınızda herhangi bir utilization (cpu, ram kullanımı) gözükmez. Bir süre sonra havuz yeterli boyuta ulaştığında (yeni thread açılmıyor ise) problem ortadan kalkar ancak yazılım restart edildiğinde yeniden ortaya çıkar.
Nasıl çözülür ?
Kısa cevap; zor.
Uzun cevap ise;
Mümkün olduğu kadar yazılımı asenkron çalışmaya uyarlamak ve senkron işlemlerden kaçınmak en iyi çözümdür. Bazen hızlı çalışsın niyetiyle yeni thread açmaya neden olan (Task.Run, Task.Factory.StartNew, ThreadPool.QueueUserWorkItem) yerde aynı thread içerisinde yapmak daha uygun olacaktır.
Geçici çözüm ise;
Uygulama açılırken thread pool adedini daha büyük bir rakama çekmek geçici bir çözüm olabilir, böylece kıtlığı engelleyecek bir stok yaratmış olursunuz ancak bu adet çok yüksek rakamlara yükseltildiğinde uygulama performansının düşeceğini söyleyebilirim.
ThreadPool.SetMinThreads(32, 16);
Problemle ilgili daha detaylı bilgilere aşağıdaki linklerden inceleyebilirsiniz;