MultiThreading bilgisayar biliminde, bir programin kendini es zamanli birden fazla is parçasina ayirabilmesinin bir yoludur. Multithreading çalisma kabaca bir programda ayni anda birden fazla islem yapabilme yetenegi olarak tanimlanabilir. Bilgisayarda yapilan tüm islemler geçici bellek dedigimiz RAM’de tutuldugundan, bir programi çalistirip RAM’e yüklendigi andan itibaren ki haline de "Proses(Islem)" ismini vermekteyiz.
Bilgisayarin bir islemi (prosesi) çalistirmasini yarayan birimine CPU(Mikro Islemci) diyoruz. Mikroislemci (CPU), herhangi bir x aninda sade ve sadece bir adet prosesi çalistirabilir. Iste CPU’larin bu çalisma biçimine Single Processing(Tek Islemli Çalisma) denilmektedir. (Ek bilgi olarak; CPU’yu bu sekilde kullanarak çalisan isletim sistemlerine ise Single Processing(Tek Islemli) Isletim Sistemi denilmektedir.)
MultiThreading ve islem arasindaki fark bir isletim sisteminden digerine degismekle birlikte genel olarak threading olusumu ve kaynaklari paylasmasi açisindan islemden'den ayrilir.Çoklu thread'ler paralel olarak pek çok bilgisayar sisteminde uygulanabilir. Tek islemci kullanildigi durumlarda MultiThreading' li uygulama zaman dilimleme ile gerçeklestirilir; tek islemci fakli threading' ler arasinda çok hizli geçis yapar ve bu durumda islemler gerçekte olmasa bile es zamanli kosuluyormus izlenimi verir. Çok islemcili sistemlerde farkli threading' ler farkli islemciler üzerinde es zamanli olarak çalisabilir.Günümüzde tek islemli CPU’lar yerlerini Multi Processing(Çok Islemli Çalisma) yahut Multi Tasking(Çok Görevli Çalisma) CPU’lara birakmistir.
Yukaridaki ekran görüntüsüne bakarsaniz eger ayni anda(x aninda) birden fazla process çalismaktadir. Iste bunu yapabilen Multi Processing yahut Multi Tasking özelliklerdir.
Birden fazla processin ayni anda çalisiyormus gibi gözükmesinin nedeni isletim sistemine ait özel bir mekanizmanin özelligidir. Bu mekanizma, processleri belirli araliklarla(milisaniye) periyodik olarak çalistirmaktadir ve bu yönteme Zaman Paylasimli Çalisma denilmektedir.
Bu mekanizma sayesinde birden fazla process CPU tarafindan belirli bir süre ile sirali olarak çalistirilmaktadir. Çalisma süresi her sistem için farklilik gösteren bu süreye Quanta Süresi denmektedir.(Örn; Win32 sistemleri için quanta süresi 20 milisaniyedir.)
Yukaridaki paragrafta birden fazla process CPU tarafindan belirli bir süre ile sirali olarak çalistirilmaktadir, demistik. Iste sirasi gelen processin çalismasinada "Prosessin Çizelgeye Girmesi" denmektedir.
Isletim sistemleri processleri sirali olarka çalistirabilmek adina Döngüsel Çizelgeleme isimli bir algoritma kullanir. Döngüsel Çizelgeleme ile bir processin durdurulup siradaki processin çalistirilmasi ise "Görevler Arasi Geçis(Task Switch)" olarak adlandirilir.
Sirali bir sekilde çalistirilacak processlerin önceligi bu mekanizmada önemlidir. Örnegin, Wind32 sistemleri processleri 0 – 31 araliginda farkli öncelik düzeyine göre siniflandirmaktadir. Eger ki farkli öncelik düzeylerine sahip prosesler bir arada çalismak zorunda kalirlarsa, çalisma sekli ve algoritmasi farkli olacak, döngüsel çizelgeleme degil Öncelikli Döngüsel Çizelgeleme algoritmasi kullanilacaktir.
Pek çok modern isletim sistemi bir is düzenleyicisi yardimiyla hem zaman dilimleme hem de çok islemcili thread' lemeyi desteklemektedir. Isletim sistem çekirdegi (kernel) sistem çagrilari vasitasi ile programciya thread' leri kontrol etme imkâni saglamaktadir. Bunun yoklugunda programlar, zamanlayicilar, sinyaller veya diger yöntemleri kullanarak kendi çalismalarini sonlandirabilirler. Bunlara user-space thread' ler denir.
Bizde yazdigimiz programlarda ayni anda birden fazla islem yaptirmak istiyorsak Threadleri kullanabiliriz. Farkli threadler ayni fonksiyonu kullanabilecegi gibi her thread farkli bir is yapan ayri fonksiyonlarida kullanabilir. Threadleri kullanarak basit bir uygulama yazalim.
Dim thread1 As New Thread(New ThreadStart(ProgressBar1Doldur))
Dim thread2 As New Thread(New ThreadStart(ProgressBar2Doldur))
Dim thread3 As New Thread(New ThreadStart(ProgressBar3Doldur))
thread1.Start()
thread2.Start()
thread3.Start()
Yukaridaki kodda gördügünüz gibi ilk olarak yeni bir thread olusturuyor ve bu threadin kullanacagi fonksiyonu da ThreadStart nesnesi içinde yaziyoruz. thread1.Start() dedigimiz zaman ThreadStart içine yazmis olugumuz kodlar ilgili thread tarafindan islenmeye baslar.
Public Sub ProgressBar1Doldur()
While progressBar1.Value < 100
progressBar1.Value += 1
Thread.Sleep(10)
End While
End Sub
Public Sub ProgressBar2Doldur()
While progressBar2.Value < 100
progressBar2.Value += 1
Thread.Sleep(60)
End While
End Sub
Public Sub ProgressBar3Doldur()
While progressBar3.Value < 100
progressBar3.Value += 1
Thread.Sleep(50)
End While
End Sub
Her thread olusturulurken belirlenmis olan fonksiyon içindeki kodlari çalistirir ve fonksiyonda ki islemler bittigi zaman otomatik olarak sonlanir.
Asagidaki formu dizaynini yapip yukaridaki gibi bir kod yazar ve derlerseniz "Çapraz is parçacigi islemi geçerli degil: 'progressBar1' denetimine olusturuldugu is parçacigi disinda baska bir is parçacigindan erisildi." Diye bir hata alirsiniz. Bunun sebebi threadlerin senkronlanmadigindandir. Bir threadin kullandigi bir nesneye baska bir thread erismeye çalistigi anda yukaridaki hatayi alirsiniz. Bu hatayi almamak için threadleri senkron olarak çalistirmaniz gerekiyor. Threadleri senkron olarak çalistirma konusuna daha sonra girecegiz. Simdilik yukaridaki hatayi engellemek için threadleri çalistirmadan ince asagidaki kodu ekleyin. Bu kod thread çakismalarini görmezden gelir ve program çalismasini devam eder.
System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = False
Dim thread1 As New Thread(New ThreadStart(ProgressBar1Doldur))
Dim thread2 As New Thread(New ThreadStart(ProgressBar2Doldur))
Dim thread3 As New Thread(New ThreadStart(ProgressBar3Doldur))
thread1.Start()
thread2.Start()
thread3.Start()
Birde kronometre uygulamasi yapalim. Bir uygulamada ayni anda iki kronometre çalistiralim ve sonucu gözlemleyelim.
Uygulamada görüldügü gibi SingleThread ve MultiThread kullanarak baslat tusuna basarak uygulamalarimizi deniyelim. SingleThread formumuzda kronometre uygulamalarini baslattigimizda Kronometre 1 uygulamasi baslayacak ama Kronometre 2 uygulamasi basla butonuna bastigimiz halde baslamayacaktir ayrica program kronometre 1 islemini bitirene kadar donma islemi yasayacaktir. Fakat MultiThread formumuzdaki kronometre uygulamalarini çalistirdigimizda programimiz sorunsuzca çalismaya baslayacaktir.
MultiThread.vb kodlarimiz;
Imports System
Imports System.Threading
Imports System.Runtime.InteropServices
Public Class MultiThread
Dim t1 As New System.Threading.Thread(AddressOf Me.MultiThread1)
Dim t2 As New System.Threading.Thread(AddressOf Me.MultiThread2)
Private Sub MultiThread_Load(sender As Object, e As EventArgs) Handles MyBase.Load
CheckForIllegalCrossThreadCalls = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
t1.IsBackground = True
t1.Start()
'MultiThread1()
End Sub
Public Sub MultiThread1()
For index = 1 To 100000
Me.Label1.Text = index.ToString()
Me.Refresh()
System.Threading.Thread.Sleep(50)
Next
End Sub
Public Sub MultiThread2()
For index = 1 To 100000
Me.Label2.Text = index.ToString()
Me.Refresh()
System.Threading.Thread.Sleep(50)
Next
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
' is parçaciginin arka plan is parçacigi olup olmadigini belirten bir deger alir veya ayarlar
t2.IsBackground = True
t2.Start()
'MultiThread2()
End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
t1.Suspend()
Button1.Enabled = False
Button3.Enabled = False
Button4.Enabled = True
End Sub
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
Button1.Enabled = False
Button3.Enabled = True
Button4.Enabled = False
t1.Resume()
End Sub
End Class
SingleThread.vb kodlarimiz;
Imports System
Imports System.Threading
Public Class SingleThread
Private Sub SingleThread_Load(sender As Object, e As EventArgs) Handles MyBase.Load
CheckForIllegalCrossThreadCalls = False
End Sub
Public Sub SingleThread1()
For index = 1 To 10000
Me.Label1.Text = index.ToString()
Me.Refresh()
Next
End Sub
Public Sub SingleThread2()
For index = 1 To 10000
Me.Label2.Text = index.ToString()
Me.Refresh()
Next
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
SingleThread1()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
SingleThread2()
End Sub
End Class
Abort Metodu
Çalisan Thread’in programatik olarak durdurulmasi yani çizelgeleme disina çikartilmasi için kullanilir. Lakin çagrildigi anda hemen threadin çalismasini durdurmaz. Bunun sebebi, Start metodu gibi asenkron bir sekilde çalismasidir.
Abort metodu ile durdurulmus bir Thread, Start metodu ile tekrardan baslatilamaz.
Suspend ve Resume Metodu
Suspend metodu ile Thread geçici olarak durdurulabilir. Yani çizelgeleme disina çikartilabilir. Suspend ile geçici olarak durdurulmus Thread Resume metodu ile kaldigi yerden devam ettirilebilir.
Burada dikkatinizi çekerim ki, Multi Thread yaklasimlarda bu iki metodun kullanimindan mümkün mertebe kaçinmanizi tavsiye ederim. Bunun nedeni, Suspend ile durdurulmus bir threadin diger threadlar ile paylasimli olarak kullandigi kaynaklarda çikardigi Deadlock diye nitelendirdigimiz muhtemel sorunlardir.
Deadlock Nedir?
Hemen yeri gelmisken Deadlock üzerine biraz konusalim.
Deadlock aslinda processlerin hiçbir zaman ulasamayacaklari bir birim yahut kaynaga ihtiyaç duymalari durumudur. Durum böyle olunca processlerin askida kalmalari diye özetleyebiliriz. Yani bir processi askida birakacak her islem olasi deadlock durumuyla karsilasmamizi saglayabilir. Örnegin; bir process bir diger processin isini bitirmesini beklerken ayni sekilde o processinde diger processin isini beklemesi durumudur. Yani çikmaza girmektir.
Sleep Metodu
Aslinda bizlere fazla yabanci olmayan Sleep metodu, teknik olarak aldigi milisaniye cinsinden deger kadar threadi durdurmamizi saglayan(çizelgeleme disina çikartan) static bir metotdur.
Burada dikkat etmeniz gereken nokta, Sleep metoduna verilen süre sonunda thread tekrar devreye giremeyebilir. Bunun nedeni, Win32 sistemlerinin Real Time(Gerçek Zamanli) isletim sistemleri olmayisidir.
Join Metodu
Düsünün ki, bir threadi sonlandirmak istiyorsunuz ama ilgili thread içerisinde kullanilan diger threadlarin son bulmasi sartiyla…
Iste Join metodu bu islevi görmektedir.
Verilen süre periyodunda threadi kontrol eden ve geriye durumla ilgili bool bir deger döndüren Join metodunu yukaridaki gibi while döngüsü araciligiyla kullanarak ilgili threadi takip edebilir ve islemlerimizi gerçeklestirebiliriz.
ThreadState Propertysi
Ilgili threadin herhangi bir x anindaki durumunu bizlere vermektedir.
Priority Propertysi
Threadin öncelik düzeyinin belirlenmesini saglar. Varsayilan olarak Normal degerine sahiptir.
IsAlive Propertysi
Threadin herhangi bir x aninda aktiflik durumunu verir.
IsAlive propertys’i üzerinde çalistirilan kanal halen çalisiyorsa true degerini aksi halde false degerini döndürür bu da bize avantajlar saglamaktadir. IsBackGround özelligi ise kanalin arka planda çalismasini gerçeklestirir kanalin arkaplanda çalismasini istiyorsaniz IsBackground’a true degerini atamalisiniz.
' value adinda boolean türünde bir degisken olusturup thread;imizin arka planda çalis çalismadigini kontrol ediyoruz.
Dim value As Boolean
value = t1.IsBackground
Label3.Text = value.ToString
Parçaçik Öncelikleri
Kanallardan bahsederken unutmamiz gereken kanallarin önceligidir. Bir kanalin önceligini CPU belirler. Düsük öncelikli kanallar az CPU zamani, yüksek öncelikli kanallar ise daha fazla CPU zamani gerektirirler. Kullandigimiz kanallar varsayilan bir öncelik atarlar. Varsayilan olarak tüm parçaçiklar islemci tarafindan esit zamanlanir. Tabiki bu önceligi bizim atamamizda mümkün ve böylece daha fazla performans elde edebiliriz. Bunun içinde Thread sinifinin üyesi olan Priority özelligini kullanabiliriz.
5 adet öncelik atamasi mevcuttur. Bunlar sirasiyla;
Highest
AboveNormal
Normal
BelowNormal
Lowest
Olusturdugumuz kanala biz bir öncelik atamassak varsayilan olarak Normal atanacaktir.
t1.Name = "ThreadOne"
t2.Name = "ThreadTwo"
t1.Priority = ThreadPriority.Highest
t2.Priority = ThreadPriority.BelowNormal
t1.Start()
t2.Start()
Olusturdugumuz kanala biz bir öncelik atamassak varsayilan olarak Normal atanacaktir. Öncelik atamasi ile ilgili açikca önceliklerin ve CPU zamanini test eden bir örnek yapalim ve bu zamanlamayi görelim.
Konsoldan çalisan örnegimiz:
Imports System.Threading
Module ThreadingPriority
Dim loopSwitchValue As Boolean
Dim threadOne As System.Threading.Thread = New System.Threading.Thread(AddressOf ThreadMethod)
Dim threadTwo As System.Threading.Thread = New System.Threading.Thread(AddressOf ThreadMethod)
Sub Main()
threadOne.Name = "ThreadOne"
threadOne.Priority = ThreadPriority.Highest
threadTwo.Name = "ThreadTwo"
threadTwo.Priority = ThreadPriority.BelowNormal
threadOne.Start()
threadTwo.Start()
'20 Saniye
Thread.Sleep(20000)
LoopSwitch = False
Console.ReadLine()
End Sub
Sub New()
loopSwitchValue = True
End Sub
WriteOnly Property LoopSwitch() As Boolean
Set(ByVal value As Boolean)
loopSwitchValue = value
End Set
End Property
Sub ThreadMethod()
Dim threadCount As Long = 0
While loopSwitchValue
threadCount += 1
End While
'Console.WriteLine("{0} with {1} priority " & "has a count = {2}", threadOne.Name, threadOne.Priority.ToString(), threadCount.ToString())
Console.WriteLine("{0} with {1} priority has a count = {2}", System.Threading.Thread.CurrentThread.Name, System.Threading.Thread.CurrentThread.Priority.ToString(), threadCount.ToString("N0"))
End Sub
End Module