Çoklu Thread Kullanımı (Multithreading)

En basit tanımıyla: çalıştırdığınız her program işletim sistemi tarafından bir adet process olarak adlandırılır. Process adres alanı içerisinde en az bir tane olmak şartı ile bir çok thread aktif halde bulunabilir. Thread işletim sistemi tarafından çalıştırılmak üzere işlem gören ve işleyici zamanı ayırımı yapılan çalışır kod parçalarıdır. Thread bağlı olduğu process 'in herhangi bir kod parçasını işletebilir, aynı anda birkaç thread process 'in farklı yada aynı kod parçalarını eş zamanlı olarak işletiyor olabilir.

Thread 'leri kullanarak çalışan programlarımız içerisinde aynı anda birçok farklı iş yaptırabiliriz. Aslında birden fazla işleyicimiz olmadığı sürece 'aynı anda' iş yapma durumunu gerçekleştirmek imkansızdır. İşletim sistemi thread 'ler arasında devamlı olarak geçişler yaparak sanki aynı anda işletim yapılıyormuş hissi verdirmektedir. Peki bu durumda programlarımızda birden çok thread kullanmak nasıl işimize yarayacak? Programlarımızda zaman zaman çeşitli girdi çıktı birimleriyle iş yapan yordamlar yazarız, veya kullanıcıdan çeşitli bilgiler bekleriz. Bu bekleme durumlarında programlarımız başka işlemler ile uğraşamazlar. Çünkü ana ve tek olan program thread 'i o anda bekleme durumunda (blocked state)  kalmaktadır. Bu durumda iken aynı anda çalışan ikinci bir thread ile bu zamanı kullanacak yararlı işler yapılabilir. Kısacası thread 'ler uygun yerlerde kullanıldıkları zaman programlarımızın performansını arttırabilirler.

Birden fazla Thread kullanmak için işletim sistemi desteği ve kullandığımız dilin thread 'leri desteklemesi gerekir. Biz burada Windows platformu için thread kullanımına örnek vereceğiz. Yazılım dili olarak ise Visual C++ kullanacağız.

Windows altında thread oluşturmak, denetlemek ve thread leri yönetmek için bir çok yordam bulunmaktadır. Hatta çoğu işlem için birden fazla aynı işi yapan yordama da rastlanır. Öncelikle çoklu thread kullanımını (multithreading) destekleyen bir uygulama geliştirmek için derleyicinizde bazı ayarlamalar yapmanız gerekmekte. Visual C++ 'ı açın ve Win32 Console Application olarak yeni bir proje oluşturun. Daha sonra Project | Settings | C/C++ | Category | Code Generation bölümüne girin ve burada "Use runtime library" bölümünde "Multithread-DLL" seçeneğini seçin.

Şimdi yeni bir cpp kütüğünü projenize ekleyin ve aşağıdaki kodu bu kütüğe yazın.

#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

void printer(void *param);

int main(int argc,char **argv) {

    _beginthread(printer,0,"thread -- 1");
    _beginthread(printer,0,"thread -- 2");

    while (getch() != 'q');

    return 0;
}

void printer(void *param) {
    while (true)
        printf("%s\n",(char*)param);
}

Bu program ana program thread 'i yanısıra 2 adet daha thread yaratıyor, daha sonra ana program thread 'i içerisinden kullanıcının q tuşuna basmasını beklerken diğer çalışan thread 'ler isimlerini ekrana yazdırıyorlar. Kullanıcı q tuşuna basınca ana thread sonlanıyor ve dolayısıyla diğer iki thread 'de otomatikman sonlandırılıp program işleyişi tamamen sona eriyor. Şimdi satır satır kodumuzu inceleyelim:

#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

Yukarıdaki satırlar ile gerekli olan include kütükleri belirtiliyor. Her windows programında ihtiyaç duyacağımız windows.h 'ın yanısıra thread işlemleri için process.h, giriş çıkış ve çeşitli yardımcı rutinler içinse diğer include işlemeleri yapılıyor.

void printer(void *param);

Bu satır ile thread fonksiyonu olarak kullanacağımız yordamın tanımını veriyoruz. Thread fonksiyonu içerisindeki kodlar yaratılan thread tarafından işletime girerler. Bu fonksiyon geri dönüş değeri void olmalı, parametre olarak ise void* türünden bir değer alabilecek özelliğe sahip olmalıdır.

_beginthread(printer,0,"thread -- 1");
_beginthread(printer,0,"thread -- 2");


_beginthread fonksiyonu programımız içerisinde yeni bir thread yaratmamızı sağlar. İlk parametre olarak thread fonksiyonun adını (yani adresini) alır. İkinci parametre yeni thread için ayrılacak stack alanı miktarını belirtir. Bu değerin 0 olması durumunda işletim sistemi stack alanının boyunu kendi belirler. Son parametre ise thread fonksiyonuna gönderilecek veriyi içerir. Bir veri göndermek istemezsek buraya NULL yazabiliriz. Yukarıda 2 adet thread yaratıyoruz. İlk thread 'e "thread -- 1" , ikinci thread 'e ise "thread -- 2" dizisini gönderiyoruz. Fonksiyon çağırımları yapılur yapılmaz yeni thread 'ler oluşturulacak ve thread yordamları ana program thread 'i ile beraber eş zamanlı olarak işletime alınacaklardır.

while (getch() != 'q');

return 0;

while döngüsü ile kullanıcı tarafından q tuşuna basılıncaya kadar beklenmesi sağlanılıyor. Kullanıcı q tuşuna basılınca program devam ediyor ve return 0; ile sonlanıyor. Ana program thread 'i q tuşunu beklerken diğer yarattığımız thread 'ler ise işletimlerine devam edeceklerdir.

void printer(void *param) {
    while (true)
        printf("%s\n",(char*)param);
}

Yukarıda thread fonksiyonu verilmektedir. Bu fonksiyon thread yaratılır yaratılmaz sadece bir defa çağırılır. Bu yordamı yeni thread 'iniz için main() yordamı olarak düşünebilirsiniz. Yordamdan geri dönüldüğü zaman thread sonlanacaktır. Devamlı olarak thread 'in sürmesini istiyorsanız bu yordam içerisinde sonsuz döngüye girmelisiniz. Biz örnek thread yordamımızda sonsuz döngü içerisinden ekrana devamlı olarak thread 'in adını (thread 'e gönderilen diziyi) ekrana yazdırıyoruz. Ana program thread 'i sonlanınca bu iki thread 'imizde otomatik olarak sonlanacaktır.

Gördüğünüz gibi birden fazla thread kullanmak aslında hiç de zor değil. Birkaç satırlık bir kod ile bu imkandan faydalanabilirsiniz. Bazı durumlarda birden çok thread kullanmak zorunda kalacaksınız. Örneğin programlarınız içerisinde socket 'ler kullanarak veri alışverişi yapıyorsanız, arka fonda ses çaldırıyorsanız, uzun bir işlem sürerken bir yandan kullanıcının müdahele etmesine izin veriyorsanız threadleri kullanmak zorundasınız demektir.

Oluşturduğunuz thread 'ler programınızın (processs'in) adres ve veri alanlarını kullanır. Yani bellekteki buluna bir kod parçası üzerinde işlem görülürken veri parçaları da ortak olarak kullanılır. Dolayısı ile örneğin tanımladığınız global bir değişkene bir thread içerisinde herhangi bir değer verdiğiniz zaman diğer tüm thread ler içerisinde bu değer verdiğiniz şekli ile okunmaktadır. Bu durum bazı karışıklıklara sebep olabilir. Farklı thread 'ler içerisinden ortak verilere erişim yaparken, özellikle yazma işlemleri ile uğraşırken kitleme mekanizmasını kullanmalısınız. Bu mekanizma ile sıra ile ortak veriye erişimi denetleyebilirsiniz.

int x;

Şeklinde tanımlanmış ortak bir veriniz olsun (global veri). Programınızıda thread leri yaratmadan önce kilit mekanizmasını etkinleştirmeniz gerekir. Bunun için yine global olarak kilitleme işleminde kullanılacak özel veri yapısını oluşturun:

CRITICAL_SECTION mutex;

Daha sonra program içerisinden:

InitializeCriticalSection(&mutex);

Çağırımını yaparak kilit mekanizmasını etkinleştirin. Bundan sonra thread 'lerinizi yaratabilirsiniz. Thread 'leriniz içerisinden x verisine herhangi bir şekilde erişeceğiniz zaman: EnterCriticalSection(&mutex) yordamını çağırmalı, daha sonra veriye erişmeli (okuma yada yazma) ve son olarak LeaveCriticalSection(&mutex) çağırımını yapmalısınız. Örneğin x versini arttırmak istediğimizde:

EnterCriticalSection(&mutex);
x++;
LeaveCriticalSection(&mutex);

Şeklinde bu işlemi yapmak durumdayız. Size bol thread 'li programlar diliyorum :). Anlamadığınız yada takıldığınız bir konuda sitemiz forumlarını kullanarak bana sorular yöneltebilirsiniz.

Deniz Aydınoğlu :: 2002 :: www.oyunyapimi.org




Bu haberin geldigi yer: oyunyapimi.org
http://www.oyunyapimi.org

Bu haber icin adres:
http://www.oyunyapimi.org/modules.php?name=Sections&op=viewarticle&artid=12