Thread etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Thread etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

22 Nisan 2015 Çarşamba

Thread'i Durdurmak

Giriş
Thread'i kolay ve temiz bir şekilde durdurmak her programlama dilinde sıkıntıdır. Aşağıda notlarım var.

Worker Thread
Bu tür thread'leri durdurmanın en temiz yolu, kuyruğa özel bir job eklemek. Örnek
void SimpleWorkQueue::tellAllThreadsToStop()
{
    std::unique_lock<std::mutex> locker(lock);
    stopping = true;

    for(std::thread& thread: workers)
    {
        work.push_back([](){ throw TerminateThread(); });
    }
}


// Now our main thread loop.
// looks like this.
// Note: We can remove 'finished' from everywhere.
void SimpleWorkQueue::workerAction()
{
    while(true)
    {
        std::function<void()> item = getNextWorkItem();
        try
        {
            item();
        }
        catch(TerminateThread const& e)
        {
            break;
        }
        catch(...)
        {
        }
    }
}

WIN32
TerminateThread() asla kullanılmamalı. Win32 ile bir thread sınıfı gelmediği için durdurma kontrolünü kendimiz yazmalıyız. Thread'i başlatmadan önce bir event yaratırız. Event'ler aynı bir bayrak gibi kullanılabilir.
HANDLE hevent_killme = CreateEvent(...);
Daha sonra bu event thread'e geçilir.

static DWORD WINAPI thread_callback (LPVOID param)
{
  ...
  while(WaitForSingleObject(hevent_killme, 0) != WAIT_OBJECT_0)
  {
    // do stuff
  }

  return 0;
}
Thread'i durdurmak isteyen event'i tetikler.
void killthread (void)
{
  SetEvent(hevent_killme);
  WaitForSingleObject(hthread_the_thread, INFINITE);
  CloseHandle(hevent_killme);
  CloseHandle(hthread_the_thread);
} 

Java
Thread.stop
Bu metod depricate edilmiş ve kullanılmamalı. Sebebi şu:

This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior.

Thread.Interrupt
Bir thread'i durdumanın en kolay ve temiz yolu Thread.Interrupt () metodunu çağırmak.

An interrupt is an indication to a thread that it should stop what it is doing and do something else. It's up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate.


Thread.Interrupt Ne Yapar?
Metod aslında sadece bir bayrağı kaldırır. Metod tamamen işbirliği esasına dayanır. Bir thread asla bir başka thread'i durdurmaya zorlayamaz. Diğer thread'in de gönül rızası göstererek durmayı kabul etmesi gerekir.


Interrupts this thread. First the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

If this thread is blocked in an I/O operation upon an interruptible channel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a ClosedByInterruptException.

If this thread is blocked in a Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeup method were invoked.

If none of the previous conditions hold then this thread's interrupt status will be set.

Thread.interrupted
Çalışan thread döngü içinde interrupted() metodunu ile durdurulmasının talep edilip edilmediğini kontrol etmeli. Metodun açıklaması şöyle. Thread bu metodu çağırdıktan sonra bayrak indirilir.

Eğer thread kendi içinde halen bu bayrağı kontrol ediyorsa, yine thread içinde Thread.currentThread.interrupt() ile bayrağı tekrar kaldırmak gerekebilir.

static boolean interrupted(): Tests whether the current thread has been interrupted.
Basit bir örnek
public void run()
{
    while(!Thread.interrupted)
    {
     ...
    }
}

Thread.isInterrupted
Eğer başka bir thread'in interrupt edilip edilmediğini anlamak istersek isInterrupted() metodu kullanılır

boolean isInterrupted(): Tests whether this thread has been interrupted.

InterruptedException
Eğer interrupt edilen thread bir nesne üzerinde bloke olmuş bekliyorsa InterruptedException alır.

Thread.isInterrupted true dönerse InterruptedException atılması öneriliyor. Ancak interrupt edildiğimizi anladıktan sonra neden exception atmak lazım anlamadım.
if (Thread.interrupted()) throw new InterruptedException(); 


26 Ocak 2015 Pazartesi

GUI ve Thread

GUI çatısılarının tamamı tek thread'le çalışırlar. Dolayısıyla arka planda çalışan bir thread'den GUI'yi güncellemek her zaman dikkatli olmayı gerektirir. Aşağıda konuyla ilgili bazı notlarım var.

.Net

WPF
Aşağıdaki örnekler WPF ile ilgili

QueueUserWorkItem metodundan UI thread'e mesaj yollamak
UI Thread'e mesaj yollamak için Dispatcher.Invoke() veya Dispatcher.BeginInvoke() metodlarından birisi kullanılabilir

Burada sorusunda da açıklandığı gibi System.Windows.Threading.Dispatcher.BeginInvoke(Delegate) metodu worker thread içinde çağırılınca UI thread'e bir mesaj yollar. Mesaj UI thread içinde çalıştırılır. Worker thread ise BeginInvoke metodundan sonraki kodu çalıştırmaya devam eder.
Bir başka örnek:

WPF'i Başka Bir Thread İçinde Çalıştırmak
Burada birden fazla WPF Dispatcher threadini başlatma yolu gösteriliyor. Open window inside a thread başlıklı yazı da okumaya değer.

WinForms
Winforms bileşenlerini sadece belli metodları başka bir thread içinde çağırılabilir. Bunlar Invoke(), BeginInvoke(),EndInvoke() ve CreateGraphics() metodlarıdır.

BackgroundWorker
BackgroundWorker yazısına taşıdım.

Thread Sınıfından GUI'ye Mesaj Yollamak
Control sınıfının Invoke metodu kullanılır. Örnek:
public class MyFormControl : Form
{
    public delegate void AddListItem();
   
    public MyFormControl()
    {
        myDelegate = new AddListItem(AddListItemMethod);
    }
    public void AddListItemMethod()
    {
    }
}

public class MyThreadClass
{
    public void Run()
    {
        myFormControl1.Invoke(myFormControl1.myDelegate);
    }
}
Control sınıfı Örnekleri

CheckForIllegalCrossThreadCalls alanı

Bu alana false değeri atanırsa, bir baaşka thread'den GUI'ye erişildi uyarısı alınmaz.


20 Ekim 2014 Pazartesi

ACE_Thread_Manager ile yeni thread yaratmak

Not : Bu yazı ile ilgili olarak pthread_self() ile gettid() arasındaki fark başlıklı yazıya göz atabilirsiniz.
Not : Posix ile yeni thred yaratmak için pthread_create() metodunu kullanmak gerekir. Bu metod <errno.h> dosyasında tanımlı olan sonuçlardan birini döner. pthread_create Linux altında clone sistem çağrısını çağırır ve aşağıdaki gibi byarakları kullanır.

CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID

ACE_Thread_Manager

ACE_Thread_Manager ile yeni thread yaratmak çok kolay. Ancak spawn veya spawn_n metodlarını kullanırken verilen THR_NEW_LWP bitinin ne işe yaradığını pek anlayamamıştım.

Konuyu anlamak için önce Multithreading modellerine bakmak lazım. Bütün threadler kernel (çekirdek) tarafından yönetilirler. Bazı thread modelleri kernel içindeki verimliliği artırmak için değişik modelleri denemişlerdir.

Many To One (Çoktan Bire) Modeli

Bu kullanım şeklinde user space içinde bir çok thread açılsa bile kernel (çekirdek) içinde sadece bir thread açılıyor.
Aşağıdaki şekili buradan aldım ve durumu gösteriyor. Eskiden Java 1.1 zamanında green thread'ler de böyle çalışıyordu.


Bu modelin dezavantajlarından birisi, eğer bir thread I/O veya harhangi bir şey için (örneğin page-fault) bloke olursa diğer thread'ler de bundan etkilenip bloke oluyorlar. If one thread is busy on I/O will the entire process be blocked sorusunda benzer bir cevap görülebilir.


One To One (Birden bire) Modeli

Bu kullanım şeklinde user space içinde bir çok thread açılsa bile kernel (çekirdek) içinde sadece bir thread açılıyor. Aşağıdaki şekili buradan aldım ve durumu gösteriyor.

 Many To Many (Çoktan Çoka) Modeli
Bu kullanım şeklinde user space içinde bir çok thread açılınca kernel (çekirdek) içinde de bir çok. Aşağıdaki şekili buradan aldım ve durumu gösteriyor.

Benzer bir şekli ise buradan aldım.


 Peki hangi unix versiyonu hangi modeli kullanıyor ?

Solaris
Biraz araştırma yaptıktan sonra bu bayrağın sadece Solaris thread kütüphanesini taklit etmek için konulduğunu gördüm. Solaris threadlerini yaratırken thr_create metoduna bu bayrak geçiliyor. Solaris anladığım kadarıyla thread modeli olarak M:N (Hybrid Threading) modelini kullanıyor ve thr_create metodunun açıklamasında da yazdığı gibi :

Typically, THR_NEW_LWP adds a new LWP to the pool of LWPs running unbound threads.
Yani bu bit ile muhtemelen yeni bir kernel threadi yaratılıyor ve o da bizim user-level threadimizi çalıştırıyor.

Buradaki yazıda many-to-many modelin Solaris 9'dan önceki versiyonlarda default model olduğu yazıyor.

Bir başka yazıda ise M:N (Hybrid Threading) modelinin Solari 9'da terk edildiği bilgisini gördüm ama bunu teyit edemedim.

Linux
Ancak Linux üzerinde pthread_setconcurrency() metodu Linux User Manuel'de şöyle açıklanmış :


NOTES
       The default concurrency level is 0.

       Concurrency levels are only meaningful for M:N threading implementations, where at any moment a subset of a process's set  of  user-level  threads  may be bound to a smaller number of kernel-scheduling entities.  Setting the concurrency level allows the application to give the system a hint as to the number of kernel-scheduling entities that should be provided for efficient execution of the application.

       Both LinuxThreads and NPTL are 1:1 threading implementations, so setting the concurrency level has no meaning.  In other  words,  on  Linux  these functions merely exist for compatibility with other systems, and they have no effect on the execution of a program.
Yani Linux zaten 1:1 (Kernel-level threading) kullandığı için bu bayrağı set etmenin hiç bir faydası yok. Set edilse de edilmese de bir fark yaratmaz.

Bu arada not olarak şu bilgiyi eklemek te faydalı olacaktır sanırım. Linux üzerinde posix threadlerini kullanmak için libc (C Standart kütüphanesi) lazım.  Linux üzerindeki C standart kütüphanesi glibc'dir (GNU C Standart kütüphanesi). Bu kütüphane ise Posix threadleri için NTPL (Native Posix Thread Library) kütüphanesini kullanır. NTPL ise yukarıda da anlatıldığı gibi "Birden-Bire" modelini benimsemiştir.

Daha eski Linux sürümlerinde NTPL yerine "LinuxThreadLibrary" kütüphanesinin de kullanıldığını görebilirsiniz.

Buradaki bir sunumda Linux'un çekirdek 2.4 ile 2.6 versiyonları arasındaki threadler için kullanılan kütüphaneleri gösteren bir şekil de var.

Not: Posix threadleri işletim sisteminden daha yukarı seviyede tanımlanmış bir spesifikasyondur. Bu yüzden Posix thread'lerine verilen thread numarası ile örneğin Linux'taki thread numaraları birbirlerini tutmazlar. Sebebini anlamak için çoktan bire modeline bakmak yeterli olacaktır. Bu yüzden Linux'taki getid() metodu ile Posix'teki pthread_self() metodları farklı sayılar döndürürler.


Ancak posix threadlerine numara verme algoritmasını halen bulabilmiş değilim. Bulunca bu yazıyı güncellemeyi düşünüyorum.

Thread Priority

Yazının bütünlüğü açısından thread priority atamanın da eklenmesi gerektiğini düşünüyorum. Bu konuyla ilgili olarak Equivalent of SetThreadPriority on Linux (pthreads) başlıklı yazıya göz atılabilir. Önemli metodlar arasında sched_get_priority_max , pthread_setschedprio ve pthread_attr_getschedpolicy sayılabilir.