Normal bir program kodu sadece tek koldan akar ve aynı anda sadece bir komut çalıştırılabilir. Bu, basit programlar için yeterli olabilir ancak programın yönettiği iş sayısı arttıkça tek yönlü akış yetmemeye başlar ve programın kendi içinde parçalara ayrılıp her parçanın farklı bir görevden sorumlu olmasının sağlanması gerekir. Bir programın birden çok parçaya bölünerek yürütülmesine multithreading, oluşturulan herbir parçacığa da thread denir.
Visual Basic 6.0 çok parçacıklı programlar yazmayı mümkün kılacak özel kütüphane veya fonksiyon barındırmaz. Ancak Windowsun kernel32.dll kütüphanesinin fonksiyonları kullanılarak thread oluşturmak ve yönetmek mümkündür:
Private Declare Function POlustur Lib "kernel32" Alias "CreateThread" ( _ ByVal lpThreadAttributes As Any, _ ByVal dwStackSize As Long, _ ByVal lpStartAddress As Long, _ lpParameter As Any, _ ByVal dwCreationFlags As Long, _ lpThreadID As Long) As Long Private Declare Function PDuraklat Lib "kernel32" Alias "SuspendThread" ( _ ByVal hThread As Long) As Long Private Declare Function PDevam Lib "kernel32" Alias "ResumeThread" ( _ ByVal hThread As Long) As Long Private Declare Function PSonlandir Lib "kernel32" Alias "TerminateThread" ( _ ByVal hThread As Long, _ ByVal dwExitCode As Long) As Long Dim ParcacikId As Long Private Sub Command1_Click() ParcacikId = POlustur(ByVal 0&, ByVal 0&, AddressOf Parcacik, ByVal 0&, 0&, &O0) End Sub Private Sub Command2_Click() PSonlandir ParcacikId, 0& End Sub Private Sub Command3_Click() PDuraklat ParcacikId End Sub Private Sub Command4_Click() PDevam ParcacikId End Sub 'Yeni thread bir modül içine yazılmalı. Çünkü AddressOf sadece 'modül içerisindeki fonksiyonların adreslerini döndürebilir... Public Sub Parcacik() Do Form1.Text1.Text = Val(Form1.Text1.Text) + 1 Loop End Sub
.Netin Threads kütüphanesi, thread yaratmak ve yönetmek için kendi içinde fonksiyonlar bulundurur. Ek olarak ortak kaynakları paylaşan parçacıkların yönetimini sağlamak için mutex ve semafor kullanımına da olanak tanıyan fonksiyonlar barındırır. Bu kütüphanenin fonksiyonları sayesinde program birden fazla koldan hiçbir problemle karşılaşılmadan etkin bir şekilde çalışmasını sürdürür:
Imports System.Threading Module Module1 Sub Main() Dim Parcacik As New Thread(AddressOf YeniThread) Parcacik.Start() Thread.Sleep(3000) 'Biraz bekleyeyim, parçacık çalışsın sonra sonlanırım Console.WriteLine("Ben ana parçacığım, sonlanıyorum...") End Sub Sub YeniThread() Console.WriteLine("Ben yeni parçacığım ve işim saymak") Dim i As Integer For i = 1 To 10 Thread.Sleep(1000) Console.WriteLine("Yeni Parçacık: " & i.ToString) Next i End Sub End Module
Thread kütüphanesini yeni kullanmaya başlayanların karşılaşacağı ilk problem muhtemelen threadlerden ana thread içerisindeki nesnelerin değişkenlerini değişterememek olur. (Bu kısıtlama sadece arayüzü hazırlayan ana threadin denetiminde oluşturulan form, buton gibi görsel öğeler için geçerlidir.) Problem, görevli (Delegate) metodlar ve invoke fonksiyonu kullanarak çözülebilir:
Delegate Sub GorevliFonk(ByVal nesne As Object, ByVal deger As String) Private Sub Yaz(ByVal nesne As Object, ByVal deger As String) nesne.Text = deger End Sub Private Sub YeniThread() '...işlemler... 'Text1Box1.Text = "AtasoyWeb" --> Hata verir 'Nesne.Invoke(GorevliFonksiyon(YurutmekleGorevliOlduğuFonksiyonunAdresi),Argümanlar) Me.Invoke(New GorevliFonk(AddressOf Yaz), TextBox1, "AtasoyWeb") '...işlemler... End Sub
Zor kısmını sona bıraktım, şimdi gelelim asıl meselemize... Parçacıklar aynı anda aynı kaynağa erişmek isteyebilirler. Bu durum kontrol altına alınmazsa problemlerle karşılaşılır. Aşağıdaki örnekte farklı iki thread aynı değişken üzerinde çalıştığından, program her çalıştırıldığında TextBox3 içerisinde değişik sonuçlar elde edilir. Eğer aynı değişkene erişim problem yaratmasaydı, her iki thread Sayi değişkenini 100000er defa arttırdığı için sonuç her seferinde 200000 olurdu. Ancak bir thread Sayi değerini okuyup arttırmış ve tam yerine yazacakken işletim sistemi işlemci kullanım hakkını diğer threade verebilir. Bu durumda 2. thread Sayi değerini okur, arttırır, yazar ve bu işlemlere kendisine tanınan süre bitene kadar devam eder. Sıra tekrar 1. threade geldiğinde 1. thread kaldığı yerden devam eder ve en son okuyup arttırdığı değeri yerine yazar. Bu yüzden 2. Threadin biraz önce kendisine verilen süre boyunca yaptığı işlerin tümü bir seferliğine boşa gitmiş olur. Sonuçta Sayi değişkeni 200000 değerine program her çalıştırıldığında varamaz.
'form içerisine 1 CommandButton ve 3 TextBox yerleştirin... Imports System.Threading Public Class Form1 Private Sayi As Integer Delegate Sub GorevliFonk(ByVal nesne As Object, ByVal deger As String) Private Sub Yaz(ByVal nesne As Object, ByVal deger As String) nesne.Text = deger End Sub Private Sub Arttir() Sayi = Sayi + 1 End Sub Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Sayi = 0 Dim t1 As New Thread(AddressOf BenArttiracagim) Dim t2 As New Thread(AddressOf BenDeArttiracagim) t1.Start() t2.Start() End Sub Private Sub BenArttiracagim() Dim i As Integer For i = 1 To 100000 : Arttir() : Next 'Thread1 işini bitirince Invoke(New GorevliFonk(AddressOf Yaz), TextBox1, Sayi.ToString) ' Son durum Invoke(New GorevliFonk(AddressOf Yaz), TextBox3, Sayi.ToString) End Sub Private Sub BenDeArttiracagim() Dim i As Integer For i = 1 To 100000 : Arttir() : Next ' Thread2 işini bitirince Invoke(New GorevliFonk(AddressOf Yaz), TextBox2, Sayi.ToString) ' Son durum Invoke(New GorevliFonk(AddressOf Yaz), TextBox3, Sayi.ToString) End Sub End Class
Yukarıda bahsetiğimiz ortak kaynak kullanımından kaynaklanan sorunları çözmek için mutexlerden faydalanılır. Bir thread, başka threadlerin de kullanabildiği bir kaynağa erişmeye çalıştığında, mutex değişkenini ayarlar. Bu atama işlemci tarafından kesinlikle kesilmez; yani thread mutexi tam yazacakken işletim sistemi sırayı başka bir threade vermez. (Mutexlerle ilgili işlemler gibi kesilmeden yapılan işlemlere atomik işlem denir. Mutex ile ilgili işlemler gerçekte atomik olmadıkları halde, işletim sistemi tarafından atomik hale getirilir.) Böylece mutex ile korunan bölgeye erişmeye çalışan tüm threadler, orayı kullanan thread işini tamamlayana kadar bekletilir. Thread işini bitirdiğinde alanı serbest bıraktığını bildirmek için mutexi tekrar ayarlar. Her sırası geldiğinde bu alana girmeye çalışan threadlerden ilk sırası gelen tekrar bölgeye girer ve mutex ile bölgeyi tekrar kilitler. Yukarıda verdiğim örnekte, bir mutex tanımlanır ve Arttir subı aşağıdaki gibi değiştirilirse Sayi değişkeninin son değerinin olması gerektiği gibi her seferinde 200000e eşit olduğu gözlenebilir:
Private m1 As New Mutex Private Sub Arttir() m1.WaitOne() ' Mutex tüm threadleri bekletsin Sayi = Sayi + 1 m1.ReleaseMutex() ' Korumalı alandan çıktığını belirt End Sub
Semaforların amacı mutexlerin amacı ile aynıdır. Semaforlar mutexlerden farklı olarak birden fazla threadin aynı bölgeye girmesine izin verebilir. Mutexler semaforların iki durumlu özel halleri olarak düşünülebilir. Mutexler korudukları bölgeye sadece 1 threadin girmesine izin verirken, semaforlar korumalı bölgeye giren thread sayısını belli bir değerle sınırlamak için kullanılır... Semaforun korumalı alan için izin verdiği thread sayısına yazının geri kalan kısmında kontenjan diyelim (bazen kavramları karşılayacak kelime bulmakta zorlanıyorum):
Imports System.Threading Module Module1 Private s1 As Semaphore Sub Main() s1 = New Semaphore(0, 2) ' Semaphore(BaşlangıçtakiBoşKontenjan,ToplamKontenjan) For i As Integer = 1 To 4 Dim p As New Thread(AddressOf Parcacik) p.Start(i) Next i Thread.Sleep(2000) Console.WriteLine("Semaforda 2 yer ayrıldı") s1.Release(2) ' ana thread iki kişilik kontenjan ayırıyor End Sub Private Sub Parcacik(ByVal numaram As Integer) Console.WriteLine(numaram & ". thread: Başladım, semafor beni bekletiyor...") s1.WaitOne() ' İçerde bulunan haricindeki threadleri beklet Console.WriteLine(numaram & ". thread: alana girdim...") Thread.Sleep(4000) 'Thread zaman alan bir iş yapıyor... Console.WriteLine(numaram & ". thread: alanı bırakıyorum...") s1.Release() Thread.CurrentThread.Abort() End Sub End Module
Yukarıdaki örneğin çıktısı:
1. thread: Başladım, semafor beni bekletiyor... 2. thread: Başladım, semafor beni bekletiyor... 3. thread: Başladım, semafor beni bekletiyor... 4. thread: Başladım, semafor beni bekletiyor... Semaforda 2 yer ayrıldı 4. thread: alana girdim... 2. thread: alana girdim... 4. thread: alanı bırakıyorum... 1. thread: alana girdim... 2. thread: alanı bırakıyorum... 3. thread: alana girdim... 1. thread: alanı bırakıyorum... 3. thread: alanı bırakıyorum...
Yukarıdaki örnekte, bölgeye işlemci kullanım hakkını alan ilk iki thread girebilir. Bu yüzden threadler farklı sıralarda bölgeye girip sonlanabilirler. Yani çıktı her zaman yukarıdaki gibi olmaz.
hocam çok değerli bilgileriniz için teşekkür ederim yani şu sizin anlattığınızı ne kadar uzun zamandır arıyordum bilemezsiniz hele hele invoke olayı çok iyi oldu tekrar teşekkür ederim
çok akıllı olduğum için yorumu yarım bırakmışım benim şöyle bir sorunum var form1 textbox üzerindeki bir değeri threade nasıl alırız
misal ; port = Val(ListBox1.SelectedItem) almak istediğim değer ama burda hata alıyorum
Çözüm aynı aslında. Problem şu; bir thread, arayüzde yer alan nesnelere müdahale edemez. Ancak nesneyi oluşturan threade kendisinin yapamadığı bir işi yaptırabilir:
Private Secilen As Object
Delegate Sub GorevliFonk()
Private Sub Oku()
Secilen=ListBox1.SelectedItem
End Sub
'Threadin içinde bir yerlerde:
Invoke(New GorevliFonk(AddressOf Oku))
'Artık ListBox1.SelectedItem yerine Secilen değişkenini kullanabilirsiniz.
yardımnız için teşekkür ederim bunu öğrendiğim iyi oldu en azından checkforillegallcrossthreadcalss olayından kurtulmuş oldum