CodeGym /Kurslar /C# SELF /Çoxiplikə giriş

Çoxiplikə giriş

C# SELF
Səviyyə , Dərs
Mövcuddur

1. Giriş

Çoxipliklik bir ofisdə bir neçə işçinin paralel işləməsinə bənzəyir: biri sənədlər çap edir, digəri müştəriyə zəng edir, üçüncüsü qəhvə hazırlayır (və, əlbəttə, hamısı proqramçıdır). Əgər bizdə yalnız bir işçi olsaydı, o hər şeyi bir-bir edərdi və ofis qəhvə üçün növbədən boğulardı. Proqramlaşdırmada vəziyyət oxşardır: tək ipli proqram eyni anda yalnız bir işi yerinə yetirə bilər.

Tutaq ki, tətbiqimiz uzunmüddətli əməliyyat yerinə yetirir — məsələn, internetdən fayl yükləyir və ya böyük cədvəl hesablayır. Bu zaman hər şey "dondurulur" — düymələr basılmır, animasiya hərəkətsiz qalır, və bir pəncərədə "cavab vermir" sözləri görünə bilər.

Çoxipliklik tətbiqə bir neçə işi eyni zamanda görməyə imkan verir: interfeys reaksiya verməyə davam edir, əməliyyatlar paralel işləyir və biz "Kompüter, yenə dondu?" tərzində monoloqa başlamırıq.

Əsas anlayışlar və terminologiya

Dərinliyə getmədən əvvəl ip (thread) nədir və prosesdən nə ilə fərqlənir, onu aydınlaşdıraq.

  • Proses (Process): Öz ünvan sahəsi, dəyişənləri və resursları olan müstəqil proqram. Məsələn, Windows-da hər açılmış tətbiq ayrı bir prosesdir.
  • İp (Thread): Proses daxilində icra vahidi. Proses bir və ya bir neçə ipə sahib ola bilər və onlar eyni resursları (yaddaş, dəyişənlər) paylaşırlar.

Nə üçün çoxipliklik bu qədər sual yaradır?

Çünki ipilər həddən artıq sərbəstdirlər: onlar istənilən anda icraya başlaya, məlumatları qarışdıra, bir-birlərini kəsə və ümumiyyətlə yaddaşda xaos yarada bilərlər, əgər düzgün qaydalar saxlanılmasa. Əgər bu, tərbiyəçisi olmayan uşaq bağçasına bənzəyirsə — tamamən doğrudur! İpilərə intizam və qayğı göstərmək etibarlı çoxiplikli proqramlar yazmağın əsasıdır.

2. C# və .NET-də çoxiplikliyin tarixi və rolu

Uzaq vaxtlarda C# təkipli idi və proqramlar sadə idi. Performans tələblərinin artması, çoxnüvəli prosessorların yaranması və interfeysin dondurmadan istifadəçi üçün reaksiya verən tətbiqlər yaratma ehtiyacı ilə .NET-ə çoxiplik vasitələri əlavə olundu. Əvvəlcə klassik System.Threading.Thread var idi, sonra Task-lər, asinxron metodlar (async/await), paralel data işləmə (PLINQ) və yüksək səviyyəli sinxronizasiya primitivləri meydana çıxdı.

C# güclü platformaya çevrildi, burada çoxipliklik nadir hal deyil, gündəlik praktikadır.

Vizual: proses və ipilər

Sadə sxem belədir:


+--------------------------------------------------+
|                 Proses (sizin proqram)           |
|      +-------------+   +-------------+           |
|      |    İp 1     |   |    İp 2     |           |
|      +-------------+   +-------------+           |
|                ...                                 |
|      +-------------+                              |
|      |    İp N     |                              |
|      +-------------+                              |
+--------------------------------------------------+

Proses daxilindəki bütün ipilər ortaq dəyişənləri və resursları görür.

3. C#-də ip necə yaratmaq olar?

Ən sadə nümunədən başlayaq: Thread sinfi, System.Threading namespace-dən.

Nümunə: ikinci ipi işə salırıq

Tutaq ki, bizim uzunmüddətli işimiz var — məsələn, 1-dən 10_000_000-ə qədər ədədlərin cəmini hesablamaq. Hesablama gedərkən əsas ip istifadəçiyə salam yazsın.


using System;
using System.Threading;

class Program
{
    // İkinci vəzifə üçün metod
    static void CalculateSum()
    {
        long sum = 0;
        for (int i = 1; i <= 10_000_000; i++)
            sum += i;
        Console.WriteLine($"[İp 2] Cəmi: {sum}");
    }

    static void Main()
    {
        // İpi yaradıb metoda delegat göstəririk
        Thread thread = new Thread(CalculateSum);
        
        thread.Start(); // İkinci ipin işə salınması

        // Əsas ip işləməyə davam edir
        Console.WriteLine("[İp 1] Salam! Paralel işləyirik...");

        // Çıxışdan əvvəl ikinci ipin bitməsini gözləyirik
        thread.Join();

        Console.WriteLine("[İp 1] Hər şey tamamlandı!");
    }
}

Nə baş verəcək?
Ekranda əvvəlcə "[İp 1] Salam! Paralel işləyirik..." sətri görünəcək, sonra hesablayan ip işi bitirdikdə cəmi çap edəcək.

Tipik problem: Kim əvvəldirsə, o çap edir

Bu kodu bir neçə dəfə işə salın — çıxışın sırası fərqli ola bilər! Bəzən cəm əvvəl görünür, bəzən salam. Bu əsl çoxipliklikdir — proqramlar daha az proqnozlaşdırılabiləndir, həddən artıq əhval-ruhiyyəli pişik kimi bazar ertələri.

4. Yaddaş zonası: ipilər nə görür?

Proses daxilindəki bütün ipilər eyni dəyişənlərə daxil ola bilirlər (əgər onlar metodun lokal dəyişənləri deyilsə). Bir ipdə dəyişən dəyişdirilərsə — digər ipilər də onu görəcək!

Nümunə: ümumi dəyişən


using System;
using System.Threading;

class Program
{
    static int counter = 0;

    static void Increment()
    {
        for (int i = 0; i < 1000; i++)
            counter++;
    }

    static void Main()
    {
        Thread t1 = new Thread(Increment);
        Thread t2 = new Thread(Increment);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine($"counter = {counter}");
    }
}

counter-də nə görməyi gözləyirik? Loqika göstərir ki, 2000, çünki hər ip 1000 dəfə artırır.

Amma yox! Bir neçə dəfə işə salın — fərqli dəyərlər görəcəksiniz: 1782, 1935, 1999…
Niyə? Bu klassik Race Condition problemidir — ipilər oxuma -> artırma -> yazma arasında biri-birinin üstünlüyünü "qapır", nəticədə bəzi inkrementlər itir.

5. Faydalı nüanslar

İpilər interfeys ilə necə qarşılıqlı əlaqə edir?

Müasir desktop tətbiqlərdə (WinForms/WPF/MAUI) əsas ip qrafik interfeysi idarə edir. İstifadəçi hərəkətləri (kliklər, daxil etmə) bu ipdədir. Fon tapşırıqları başqa ipi-də işləməlidir, amma qaydalara görə digər ipilərdən interfeysə birbaşa "toxunmaq" olmaz. Bu xaosu önləmək üçün edilir.

Konsolda belə məhdudiyyət yoxdur, istənilən ipdən Console.WriteLine çağırmaq olar. Amma real tətbiqlərdə düzgün sinxronizasiya olmadan interfeys pozula bilər.

Sıralılıq və paralellik

Cədvəl — fərqləri möhkəmləndirmək üçün.

Təkipli kod Çoxipli kod
Vəzifələri növbə ilə icra edir Vəzifələr eyni anda icra oluna bilər
Uzun müddətli əməliyyatlarda UI "donur" UI reaksiya verməyə davam edir
Dəyişənləri oxumaq və yazmaq asandır Məlumatlara daxilolmanı nəzarət tələb edir
Debug etmək asandır Debug etmək çətin ola bilər

İp ilə işləyərkən vacib məqamlar

  • Ümumi dəyişənlər — ümumi risk. Yuxarıdakı kimi, bir neçə ip ümumi dəyişən istifadə edirsə — sinxronizasiya yoxdursa səhvlər mümkündür! (Detalları növbəti mühazirələrdə.)
  • İpi Join() ilə "gözləmək" olar. Join() metodu əsas ipin fon ipinin bitməsini gözləməsinə imkan verir. Nəticə gözləmək lazım olarsa istifadə edin.
  • İpi iki dəfə işə salmaq olmaz. İpin işi bitdikdən sonra onu yenidən start etmək mümkün deyil — yeni Thread obyekti yaratmaq lazım gələcək.
  • İpin bitməsi. İp onun metodunun icrası bitəndə sona çatır. İpləri məcburi "öldürmək" pis fikirdir (metod Abort() artıq zərərli və köhnəlmiş hesab olunur).

Çoxipliklik praktiki nə üçün lazımdır?

  • UI-tətbiqlər: fon yükləmə və ya hesablamalar zamanı interfeysi dondurmamaq üçün.
  • Serverlər və xidmətlər: bir neçə müştərinin sorğularını eyni zamanda emal etmək üçün.
  • Yüksək performanslı hesablamalar: böyük işi (məsələn, milyonlarla yazını) hissələrə bölüb paralel icra etmək üçün.
  • Oyunlar, simulyasiyalar, data emalı: mürəkkəb sistemləri modelləşdirmək, performans itirmədən.

Çoxiplikliyin problemləri

Çoxipliklik güc verir, amma çətinliklər də gətirir:

  • Race Condition (yarış vəziyyəti): bir neçə ip eyni məlumatı dəyişəndə nəticə təlimatların sırasından asılı olur və proqnozlaşdırılmaz olur.
  • Deadlock (qarşılıqlı bloklanma): ipilər bir-birlərini gözləyir və heç kim davam edə bilmir.
  • Starvation (ac qalma): bir ip resursa daim giriş imkanından məhrum edilir.

Bu mühazirədə biz yalnız çoxiplikliyin əsas problemlərini qeyd etdik, sonraki dərslərdə onları necə tanımaq və önləmək öyrənəcəyik. Hələlik — yadda saxlayın ki, hər şey "donubsa", günahkarlar yalnız bug-lar olmayıb, həm də ipilər ola bilər ki, "parti təşkil ediblər".

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION