23 Eylül 2013 Pazartesi

.Net ThreadPool vs. QThreadPool vs. Java ThreadPoolExecutor

Not 1: Linux Scheduler'ları başlıklı yazıda ThreadPool içinde koşan thread'lerin öncelik seviyesinin ayarlanması ile ilgili bilgi bulmak mümkün.
Not 2 : ACE_Thread_Manager ile yeni thread yaratmak başlıklı yazıda ise Process Scheduler ve kernel tarafından kullanılabilen thread modelleri ile ilgili bilgi bulmak mümkün.

.Net 4.0 ThreadPool

.Net 4.0 ile gelen ThreadPool sınıfı yaratılabilecek azami thread sayısını dinamik olarak hesaplıyor.

MSDN'nin dediğine göre :

There is one thread pool per process. Beginning with the .NET Framework version 4, the default size of the thread pool for a process depends on several factors, such as the size of the virtual address space. A process can call the GetMaxThreads method to determine the number of threads. The number of threads in the thread pool can be changed by using the SetMaxThreads method. Each thread uses the default stack size and runs at the default priority. 
4 tane Core i5 CPU'ya sahip 3 GB hafıza taşıyan sistemim için bu rakamlar :

1023  tane worker thread ve 1000 tane de I/O completion thread çıkınca bayağı şaşırdım. Bu kadar çok thread açılabileceğini hiç sanmazdım.

Aslında açılan thread sayısını da sınırlamak için bir tedbir de düşünülmüş.
ThreadPool Max Threads sorusuna verilen bir cevapta :

However, there's something else at play: the thread pool doesn't immediately create new threads in all situations. In order to cope with bursts of small tasks, it limits how quickly it creates new threads. IIRC, it will create one thread every 0.5 seconds if there are outstanding tasks, up to the maximum number of threads. I can't immediately see that figure documented though, so it may well change. I strongly suspect that's what you're seeing though. Try queuing a lot of items and then monitor the number of threads over time.

Yani ThreadPool'da akıllı davranıyor ve biraz bekliyor çünkü çoğu işin kısa sürede biteceğini düşünerek iyimser davranıyor.

ThreadPool Available Threads shows very high figures sorusunda ise kaç tane daha thread'in açılabileceğini öğrenmek için ThreadPool.GetAvailableThreads metodunun kullanımına örnek verilmiş.



QT QThreadPool

Öte yandan QT ile gelen QThreadPool sınıfı en fazla sistemdeki core sayısı kadar thread yaratıyor.

Returns the ideal number of threads that can be run on the system. This is done querying the number of processor cores, both real and logical, in the system. 

Yani benim sistemimde bu sayı 4 olacaktı ki .Net ThreadPool sıfınıfa göre sayı yetersiz görünüyor.

Bu arada QT ile yaratılan threadler posix threadleri. Thread'ler pool tarafından join edilecek şekilde yaratılıyorlar. Yani threadler yaratılırken PTHREAD_CREATE_DETACHED flag'i kullanılmıyor. Bu flag'in kullanım amacını buradan okuyabilirsiniz. Böylece thread pool tüm worker thread'lerin bitmesini waitForDone() metodunu çağırarak bekleyebiliyor. waitForDone() metodu ise aslında pthread_join metodunu çağırıyor. Aşağıda pthread_join() metodunun nasıl çalıştığını gösteren bir şekil var.


Yeri gelmişken Avoiding memory leaks in Posix Thread Programming başlıklı yazıda
ls /proc/PID/task | wc -l ve (Bu komut ile aktif threadler sayılıyor)
pmap PID | grep 10240 | wc -l (Bu komut ile yaratılan thread stackleri sayılıyor)
komutları kullanılarak pthread_join() metodu ile bitmesi beklenilmemiş thread'lerin nasıl bulunabileceği anlatılıyor.

Nitekim yaptığımız bir projede karşı cihazı SNMP ile sorgulama işinin biraz uzun sürmesi halinde QThreadPool sınıfının tıkanma noktası oluşturduğunu görüp  açılabilecek max. thread sayısını artırmak zorunda kalmıştık.

Kendi Thread Poolumuz
Aşağıda kendi thread pool nesnemizi kabaca gösteren bir kod parçası var.
class Executor {
  FIFOQueue<Task> m_List;
  FIFOQueue<Task> m_CompletedList;

  void Initialize (int threadCount){
    for (int index = 0; index < threadCount; index++) {
      Worker thread = new Worker (this);
       t.start ();
    }
  }

  bool ExecuteTask () {
    Task* pTask = m_List.Dequeue ();
    pTask->Perform ();
    m_CompletedList.Enqueue (pTask);
  }

  Task* GetCompletedTask () {
    return m_CompletedList.Dequeue ();
  }
};

class Worker : public Thread {
Executor* m_pExecutor;
  void Execute () {
    while (runFlag){
        m_pExecutoer->ExecuteTask ();
    }
  }  
};

Java ThreadPoolExecutor

ThreadPoolExecutor aslında karmaşık bir hiyerarşinin parçası. Bu hiyerarşiye kısaca bakarsak herşey Executor ve ExecutorService arayüzleri ile başlıyor.

Arayüzleri gösteren bir diğer şekli ise buradan aldım.

 Bir ExecutorService yaratmayı kolaylaştırmak için Executors sınıfı mevcut. Örneğin aşağıdaki kod parçasıyla
ExecutorService sendingService = Executors.newSingleThreadExecutor();

buradan aldığım şekildeki gibi herşeyi sırayla işleyen tek threadli bir servis yaratılabilir.



Java ile gelen ThreadPoolExecutor ise kendisi kaç tane thread açması gerektiğini hesaplamıyor bile. Her şeyi programlayacının inisiyatifine bırakmış ne kadar istersen o kadar aç ben karışmam diyor.Aşağıdaki şekli buradan aldım.



Yalnız ThredPoolExecutor sınıfının bir güzel özelliği işlemediği görevleri için bir rejection handler kullanması. Aşağıda buradan aldığım hazır kullanılabilen rejection handler sınıflarının şekli var.


Özel bir çeşit thread pool olan ScheduledThreadPoolExecutor sınıfının hiyerarşi şeklini ise buradan aldım.

Özet
Özet olarak .Net ThreadPool sınıfı ise işin bloke olabileceğini farz edip core sayısından kat kat fazla thread açılabilmesine imkan tanıyor.Çıtayı çok yüksek tutmuş.


 QThreadPool sınıfı kendine verilen işlerin çok çabuk biteceğini farz ediyor ve işin bloke olmasını kabul etmiyor. Dolayısıyla çıtayı alçak tutup azami thread sayısını düşük tutmuş.

Esasen QThreadPool sınıfını da 1023 thread açabilecek hale getirdikten sonra problem kalmıyor ancak iki thread pool sınıfı arasındaki felsefe farkına dikkat çekmek istedim.

Jaba ThreadPoolExecutor ise ben bilmem  beyim bilir yöntemini seçmiş ve her şeyi programlayıcıya bırakmış.

1 yorum: