Çoklu Thread Kullanımı (Multithreading)
(859 kelime) (865 okuma)
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
|