3. “
3
Çoxaxınlı proqramlaşdırma nədir?
Windows əməliyyat sisteminin önəmli xüsusiyyətlərindən biri də
çoxaxınlı arxitekturaya malik olmasıdır. Bəs çoxaxınlı (multithreading)
nədir? Ümumiyyətlə, proses və axın sözləri bir-biri ilə səhv salınır.
Kompayl edilib işa salınan bir programam əməliyyat sistemi içərisində
proses deyilir. Məsələn, bir kalkulyator proqramı ilə rəsm proqramı
ard-arda işə salaq: bu iki proqram əməliyyat sistemi tərəfində iki ayrı
proses olaraq görüləcək. Bu iki proqram bir-birinin işlərinə müdaxilə
etmir. Hər proses özünün yaddaş sahəsi içərisində işini davam etdirir.
Axın isə bir prosesin içərisində yerləşən və iş görən rutindir. Bir
prosesin içərisində bir neçə axın mövcud ola bilər. Axınlar daha çox
bir proses içərisində metodların ayrı ayrı kanallarda paralel olaraq
işləməsini aparmaq üçün istifadə olunur.
.
5. “
5
Çoxaxınlı proqramlaşdırmanın üstün cəhətləri
Çoxaxınlı proqramlaşdırmanın üstünlükləri aşağıdakılardır:
•Proqramın sadələşməsi (Eyni ünvan fazasından istifadə etməsindən irəli gəlir)
•Minimal vaxt sərf olunması
•Hesablamaların və giriş-çıxış əməliyyatlarını paralel yerinə yetirməsi əsasında
effektivliyin yüksəldilməsi
6. “
6
Çoxaxınlı proqramlaşdırmanın mənfi cəhətləri
Çoxaxınlı proqramlaşdırmanın mənfi cəhətləri də var:
•Eyni prosesin daxilində kəsilmə taymerinin olmaması
•Proses bloka düşən zaman onun daxilində olan axınlarında da bloka düşməsi
•Mürəkkəblik səviyyəsi
•Nüvə cədvəli ilə paralel olaraq proseslər cədvəli də mütləq yüklənməlidir
•Nüvə cədvəli vasitəsilə proseslər idarə olunmalıdır
7. “
7
Axınların sinxronizasiyası
Axınlar vasitəsilə ayrı ayrı metodları eyni vaxtda işə sala bilərik. Əməliyyat
sistemi hər axına işləməsi üçün müəyyən bir zaman aralığı verir. Bu zaman
aralığı dolanda çalışan axından çıxılıb, programın içərisindəki digər başqa
axına və ya metoda girilir. Bir metodun içərisində görülən iş nə qədər uzun
çəkirsə o metodun bağlı olduğu axına o qədər zaman sərf olunur. Bəzən bu
vaxt o qədər çox olur ki, prosesin içərisindəki başqa bir axında çalışması üçün
çox az vaxt qalır. Başladılan axınların eyni vaxtda sinxron şəkildə çalışa
bilməsi üçün .NET-də olan sinxronizasiya yollarından istifadə edilir.
Sinxronizasiyanın tam olaraq necə olduğunu göstərmək üçün aşağıdakı
misala baxaq.
8. Hər biri bir
progressbar
dolduran üç
ədəd funksiya
yaradaq
8
public void QiymetArtir1()
{
for (int i = 1; i <= 100; ++i)
{
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(10);
}
}
9. Hər biri bir
progressbar
dolduran üç
ədəd funksiya
yaradaq
9
public void QiymetArtir2()
{
for (int i = 1; i <= 100; ++i)
{
progressBar2.Value += 1;
lbThreads.Items.Add("Thread 2");
Thread.Sleep(100);
}
}
10. Hər biri bir
progressbar
dolduran üç
ədəd funksiya
yaradaq
10
public void QiymetArtir3()
{
for (int i = 1; i <= 100; ++i)
{
progressBar3.Value += 1;
lbThreads.Items.Add("Thread 3");
Thread.Sleep(150);
}
}
11. Və 3 yeni axın
yaradıb hər
birininin
daxilinə bu 3
funksiyanı
yerləşdirərək
işə salaq
11
private void btnStart_Click(object sender, EventArgs e)
{
th1 = new Thread(new ThreadStart(QiymetArtir1));
th2 = new Thread(new ThreadStart(QiymetArtir2));
th3 = new Thread(new ThreadStart(QiymetArtir3));
th1.Start();
th2.Start();
th3.Start();
}
12. “
12
Axınların sinxronizasiyası
Üç axın çalışanda aşağıdakı kimi bir görüntü ortaya çıxacaqdır. Burada görə
bilərsiniz ki, birinci axının bağlı olduğu QiymetArtir1 metodunda 10
millisaniyəlik gözləmə vaxtı varkən (Thread.Sleep(10)), üçüncü kanalın bağlı
olduğu QiymetArtir3 metodundan 150 millisaniyəlik gözləmə vaxtı var. Vaxtlar
arasındakı bu fərqdən ötrü hər axına ayrılan vaxt bir-birindən fərqlidir. Burada
prioritetli kanal th1'dir. Bu görə birinci progressbar digərlərindən daha tez
dolar.
13. “
13
.NET'də thread sinxronizasiya yolları.
Eyni anda çalışan kanallar (sinxron
thread) isə bir-birlərinin metodlarının
əməliyyatlarının bitməsini gözləyər.
Sinxronizasiya lazımlı olduğu bir başqa
vəziyyət, bir kanalın başqa bir kanalın
səbəb olduğu bir hadisəni gözləməsidir.
Bu vəziyyətdə, hadisə meydana gələnə
qədər ilk kanalı asılı vəziyyətdə tuta
biləcək bəzi üsullar vacibdir.
14. “
14
İnterlocked
Interlocked sinfi birdən çox kanal tərəfindən istifadə edilən dəyişənlər üzərində
müxtəlif əməliyyatların icra edilməsinə imkan verən bir sinifdir. Buradakı dəyişən
qiymət tipində ola biləcəyi kimi hər hansı bir obyekt də ola bilər. Interlocked sinifinin
hələlik yalnız Exchange metodu göstərici tipləri ilə işləyə bilər. Exchange'in generic
metod olaraq hazırlanmış bir overload'u da mövcuddur. Increment, decrement
metodları isə Int32, Int64 növü dəyişənlərin parametr olaraq qəbul edə bilər.
15. İnterlockedin
istifadəsi
Burada counter
global olaraq təyin
edilmiş olan int
dəyişəndir.
Increment funksiyası
ilə counter dəyişəni
idarəli artırılır. Bir
axında counter
üzərində əməliyyat
edilərkən digər bir
axın içərsində
counter üzərində
eyni anda əməliyyat
edilə bilməz.
15
public void QiymetArtirInterlocked1()
{
try
{
while (counter <= 300)
{
Interlocked.Increment(ref counter);
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(80);
}
}
catch { }
}
16. “
16
Lock
Bir axının içindəki əməliyyatların, digər bir axın tərəfindən müdaxilə edilmədən işləyə
bilməsi üçün lock açar sözü istifadə edilir. Lock ilə bloklanmış olan əməliyyatlar bir
kanal içərsində tamamlanana qədər icra olunurlar. Bu müddət ərzində başqa kanallar
lock ilə bloklanmış əməliyyatların bitməsini gözləyirlər. Proses bitəndə digər kanallar öz
metodlarını işlətməyə davam edir. Bu şəkildə sinxronizasiya təmin edilir. Lock
parametr olaraq hər hansı bir obyekt də ala bilər.
17. Ümumiyyətlə lock'a
köçürülən parametrin
public olmamasına
diqqət edilir. Lock ilə
kilidlənən obyekt public
olsa bu obyekt üzərində
əməliyyat etmək
istəyən metodlar (bir
axına bağlı olmayan) da
kilidlənir. Bu cür
qıfıllanmaya deadlocks
(sonsuz dövr) deyilir. Bu
istənməyən bir
vəziyyətdir. Public
yerinə protected və ya
private istifadə etmək
bu problemi həll edir.
17
public void DegerArttirLock1()
{
for (int i = 1; i <= 100; ++i)
{
lock (this)
{
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(10);
}
}
}
18. “
18
Lock
Şəkildə QiymetArtirLock metodlarını çağıran axınlar işlədildiyində progressbar'ların və
kanalların vəziyyəti göstərilmişdir. Diqqət etsəniz kanallar 123, 123 şəklində ard-arda
düzgün bir şəkildə çağırılır. Asinxron kanallarda isə 132, 131, 111 şəklində nizamsız
çağırılmalar olur.
19. “
19
Monitor
Monitor, axınların obyektlərə sinxron bir şəkildə əlaqəsini təmin edən bir
sinifdir. Monitor sinfi göstərici tipindəki dəyişənləri sinxronizasiya etmək üçün
istifadə edilir. Qiymət tipindəki dəyişənlər üçün monitor sinfi istifadə edilmir.
Monitor'un istifadəsi Lockun istifadəsinə bənzəyir. Monitor blokladığı kodların
başqa axınlar tərəfindən əlaqəsinə mane olar. Monitor sinifinin bəzi metodları
növbəti cədvəldə ümumiləşdirilmişdir.
20. 20
Metod Vəzifəsi
Enter, TryEnter Bloklanmaq istənən obyekt Enter ilə Monitor obyektinə yaddaşa yazılır.
Digər kanallar bu bloklanan obyektə müdaxilə edə bilməz. Obyekt artıq bağlıdır.
Monitor.Enter ilə bloklanmaq istənən kodlar üçün lock'a bənzər şəkildə başlanğıc verilə bilər.
Wait Bloklanmış obyekt üzərindəki kilidi açır. Digər axınlar obyektə əlçata bilər və onu bağlaya bilər.
Pulse, PulseAll Bir kanaldakı kilidlənmiş obyektin, üzərindəki kilidin ləğv ediləcəyini gözləmədə olan axınlara
bildirmək üçün istifadə edilir. Digər axınlar öz növbələrinə kilidi açılacaq olan obyekti qeydə
alırlar. Pulse, Exit metodundan əvvəl istifadə edilən bir metoddur və əsas məqsədi digər
axınlara kilidin açılacağını əvvəlcədən bildirməkdir.
Exit Obyekt üzərindəki kilidi açar.
Monitor.Exit ilə bloklanmak istənən kodlar üçün lock'a bənzər şəkildə sonlanma nöqtəsi verilə
bilər.
21. Monitor sinifinin
QiymetArtir
funksiyasında necə
istifadə ediləcəyi
aşağıda göstərilmişdir.
Sonsuz loops
(deadlock) ilə
qarşılaşmamaq üçün
monitorObject obyektin
üzərindəki kilid finally
blokunda hər zaman
ləğv edilir.
21
private object monitorObject = new object();
public void QiymetArtirMonitor1()
{
for (int i = 1; i <= 100; ++i)
{
try
{
Monitor.Enter(monitorObject);
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(10);
Monitor.Pulse(monitorObject);
}
finally
{
Monitor.Exit(monitorObject);
}
}
}
22. “
22
Mutex
Mutex sinfi Monitor sinifinə bənzəyir ancaq System.Threading.WaitHandle
sinifindən törəmədir və törədiyi sinifin daha asan istifadə edilə bilən bir
umumiləşmiş formasıdır. Mutex sinfi axınların ortaq istifadə edilən obyektlərə eyni
anda çatıb, eyni anda əməliyyat edilməsinə maneə törətmək üçün istifadə edilir.
Mutex sinfi ortaq istifadə edilən resurslara bir t zamanında yalnız bir axının
istifadəsinə imkan verir. Bir mutex'e sahib olan axını WaitOne metodu ilə onu
bağlayırlar və ReleaseMutex metodu ilə mutex'i sərbəst buraxar. Bir mutex istifadə
edən axın yalnız öz mutex'ini ReleaseMutex metodu ilə aça bilər. Kanallar bir-
birlərinin mutex'lərini sərbəst buraxa bilməz. Bizim misalda progressbar'ların tam
mənası ilə eyni anda irəliləmədiğini fərq edəcəksiniz. Burada mutex'i yalnız ortaq
qaynaqlara təhlükəsiz girişi məqsədi ilə istifadə edirik.
23. Bizim misalda
progressbar'ların tam
mənası ilə eyni anda
irəliləmədiğini fərq
edəcəksiniz.
23
private object mutexObject = new object();
public void QiymetArtirMutex1()
{
for (int i = 1; i <= 100; ++i)
{
mutexObject.WaitOne();
progressBar1.Value += 1;
lbThreads.Items.Add(>"Thread 1");
Thread.Sleep(10);
mutexObject.ReleaseMutex();
}
}
24. “
24
Semaphore
Semaphore sinfi .NET 2.0 framework ilə gələn yeni bir sinifdir. Burada eyni obyekt,
eyni resurs bir neçə axın tərəfindən icra oluna bilər. Bu sinif də
System.Threading.WaitHandle sinifindən törəmişdir. Semaphore sinifinin Mutex
sinifindən fərqi, fərqli kanalların bir-birlərinin Semaphore'larının kilidlərini Release
metodu ilə açabilmələridir. Bir axın semaphore'un WaitOne metodunu bir neçə dəfə
çağıra bilər. Bu kilidləri açmaq üçün ard-arda Release metodunu çağırabildiyi kimi,
Release (int) overload'unu da istifadə edə bilər. Semaphore özünü istifadə edən
kanalın identity'sine baxmaz. Bu görə fərqli kanallar bir-birlərinin semaphore'larının
WaitOne və Release metodlarını çağıra bilər.
25. Hər WaitOne metodu çağırılanda
semaphore'un sayğacı bir azaldılır. Hər release
metodu çağırıldığında isə sayğac bir artırılar.
Semaphore'un konstruktorunda sayğacın
minimum və maksimum dəyərləri təyin oluna
bilər. 25
private Semaphore semaphoreObject = new
Semaphore(0,2);
public void QiymetArtirSemaphore1()
{
for (int i = 1; i <= 100; ++i)
{
semaphoreObject.WaitOne();
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(10);s
semaphoreObject.Release();
}
}
private void btnSemaphore_Click(object sender, EventArgs e)
{
th1 = new Thread(new ThreadStart(DegerArttirSemaphore1));
th2 = new Thread(new ThreadStart(DegerArttirSemaphore2));
th3 = new Thread(new ThreadStart(DegerArttirSemaphore3));
th1.Start();
th2.Start();
th3.Start();
semaphoreObject.Release(2);
}
26. “
26
Join
Elə vaxt olur ki, əməliyyat gedən iş hissəsinin digər iş hissəsinin sonlanmasını
gözləməyi lazım olur və digər axın sonlandıqdan sonra öz işinə davam etməsi lazım
olur. Bu iş üçün join metodu bu və bənzəri digər vəziyyətlərdə istifadə edə
biləcəyimiz bir metoddur. Işlədildiyində aktiv əməliyyat digər əməliyyat ilə birləşir və
digər əməliyyat sonlandıqdan sonra bir alt sətirdən işinə davam edir.
27. “
27
Thread.Abort()
Çalışan iş əməliyyatlarının dayandırmanın bir digər yolu da Thread sinifinin Abort
metodunu çalışdırmaqdır. Bir əməliyyatı bu şəkildə dayandırmaq hər nə qədər çox
faydalı deyilsə də bəzi hallarda son alternativ olaraq müraciət edilə bilən bir həlldir.
Axının əməliyyatı Abort metodunu çağırdığımız vaxt məcbur bağlanacaqdır. Hər
hansı bir parametr almayan bu metodu çalıştırdığınızda axının vəziyyəti əvvəlcə
AbortRequested olaraq dəyişəcək. Bir müddət sonra da axın dayandırılaraq
vəziyyəti (ThreadState) Aborted olaraq dəyişdiriləcəkdir.
28. “
28
Thread.Abort()
Abort metodunun çalışamayacağı vəziyyətlər da mövcuddur:
•Sabit (static) sinif constructor'ları qətiyyən abort metodu ilə sonlandırıla bilməzlar.
•Catch / finally blokları isə İngiliscədəki deyimiylə onurlandırılmışlardır və bu
blokların içində ikən abort metodu işləməyəcək.
•Bir digər vəziyyət isə unmanaged bir kod çalıştırmışıqsa qarşınıza çıxacaq. Bu
vəziyyətdə abort dövrəyə girə bilməyəcək və bu kodun işi bitdikdən sonrakı ilk
managed kodda abort metodu dövrəyə girəcək.
29. “
29
Thread.Interrupt()
Bu metod ilə yuxu rejimində olan bir axını məcbur oyandıra bilərsiniz. Bu əslində
doğru bir həll yolu olmasa da axınlar arası qarşılıqlı əlaqədə istifadə edə biləcəyiniz
ən sadə yoldur. Bu metod ilə bir axını oyatmağınız vəziyyətində
ThreadInterruptException səhv mesajını çap edəcək. Bu metodun Abort
metodundan fərqi isə axını sonlandırmamasıdır. Abort metodu az əvvəldə bəhs
etdiyimiz kimi bir axını zorla dayandırmaq üçün istifadə olunur, Interrupt metodunu
yalnız yatmaqda olan axını oyandırmaq üçün istifadə edə bilərsiniz.