1. Davamlı çoxthreadli kodun sirri
Çoxthreadliyi eyni maşını eyni vaxtda düzəldən bir neçə işçi komandası kimi təsəvvür etsək, birinin başqasının alətini götürməsi təmirin dayanmasına səbəb olur. Koddakı paylaşılan məlumatlarla laqeyd davranmaq gizli səhvlərə gətirib çıxarır, hansı ki yalnız "savaştə" üzə çıxa bilər.
Bu mühazirədə — kart evi kimi çökməyən çoxthreadli kod necə yazmaq, və əgər nəsə səhv gedərsə hansı alətlər kömək edəcək.
1. Kritik bölmələri (lock) minimum saxlayın
Lock daxilində daha az kod olduqca yaxşıdır. Bir thread lock-u saxladığı müddətcə digərləri gözləyir.
Nümunə:
// PLOXO: Bütün biznes-logic lock daxilində — bütün threadlər gözləyir
lock(_locker)
{
// Uzun əməliyyat (paylaşılan resursa aid deyil)
Thread.Sleep(500);
counter++;
}
// YAXŞI: Yalnız minimal lazım olan əməliyyat lock içindədir
// bütün ağır iş lock xaricindədir
Thread.Sleep(500);
lock(_locker)
{
counter++;
}
Real həyat: Əgər lock daxilində şəbəkə çağırışı və ya uzun hesablamadırsa, performans kəskin düşür.
2. Lock açarı kimi universal obyektlərdən istifadə etməyin
lock(this) və ya lock(typeof(MyClass)) pis fikirdir.
Niyə? Əgər başqa biri eyni obyekti öz lock-u üçün istifadə edərsə, qarşılıqlı bloklanma və ya gizli buglar alınar. Həmişə ayrıca privat obyekt istifadə edin:
private readonly object _locker = new object();
lock(_locker)
{
// Sizin əməliyyatlar
}
Qadağan olunur: stringlər (string), public sahələr, dəyər-tip obyektləri.
3. Tutuqlanan resursları azad etmək üçün həmişə try...finally istifadə edin
Hər hansı Mutex, semafor, ReaderWriterLockSlim tutulması — finally içində azad edilməlidir.
_mutex.WaitOne();
try
{
// Kritik bölmə
}
finally
{
_mutex.ReleaseMutex();
}
4. Sinxronizasiyanı sui-istifadə etməyin
Yalnız real paylaşılan resurslara (məsələn kolleksiyalara) çıxışı sinxronizasiya edin, "hər ötürmə" üçün yox. Artıq lock-lar kodu gözləmə sıraçına çevirir.
5. Thread-safe kolleksiyalar və tiplərdən istifadə edin
.NET çoxthreadli ssenarilər üçün xüsusi kolleksiyalar təqdim edir: ConcurrentDictionary, ConcurrentQueue, ConcurrentBag, BlockingCollection və s. Onlar daxilində özlərini artıq qoruyurlar.
using System.Collections.Concurrent;
ConcurrentDictionary<int, string> users = new ConcurrentDictionary<int, string>();
users.TryAdd(1, "Vasya");
users[2] = "Petya";
6. Deadlock (qarşılıqlı bloklanma) ehtiyatlı olun
Tipik tələf — bir neçə lock-u fərqli sıralarda tutmaq.
// Thread 1
lock(obj1)
{
lock(obj2)
{
// Nəsə edirik
}
}
// Thread 2
lock(obj2)
{
lock(obj1)
{
// Nəsə edirik
}
}
Məsləhət: Bütün threadlərdə lock-ları həmişə eyni sırayla alın.
7. Mümkün olduqda immutable state (dəyişməz vəziyyət) istifadə edin
Obyekt yaradıldıqdan sonra vəziyyətini dəyişmirsə — onu hər hansı thread-dən təhlükəsiz oxumaq olar. Nümunələr: string, Tuple, DateTime, öz Read-only DTO-lar.
2. Çoxthreadlik problemlərinin diaqnostikası üçün alətlər
Sinxronizasiya səhvləri hiyləgərdir: nadir və proqnozlaşdırılmaz şəkildə ortaya çıxır. Onları tutmaq və analiz etmək üçün alətlərdən və yanaşmalardan istifadə edin.
1. Hadisələrin və threadlərin loglanması
Cari Thread.ManagedThreadId və əsas əməliyyatları loglayın — "kimin və nə vaxt" kritik bölməyə girdiyini/çıxdığını başa düşmək üçün sadə üsuldur.
Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Všel v kritik bölmə");
// ...
Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Výšel iz kritik bölmə");
Real tətbiqlər üçün Microsoft.Extensions.Logging, NLog, Serilog istifadə edin.
2. Thread Sanitizer & Race Detector
.NET-də tam "daxili" ThreadSanitizer yoxdur, amma faydalı alətlər var:
- JetBrains ReSharper — inspeksiyalar bəzi təhlükəli pattern-ləri tutur.
- Roslyn Analyzers — statik kod analizi.
- Concurrency Visualizer — gözləmələri/lock-ları və thread yükünü analiz edir.
3. Visual Studio Diagnostics Tools
Visual Studio profilləşdiriciləri göstərməyə kömək edir:
- tətbiqdaxili hansı threadlərin olduğunu;
- threadlərin harada boşdaydığını (waiting);
- harada lock və contention yaranır;
- deadlock və contention nə vaxt meydana gəlir.
Lock istifadə qrafikasını almaq üçün trace-lər götürün.
4. Dump-analitika və WinDbg
Server "asılıb"sa, proses dump-u götürün və onu WinDbg və ya dotnet-dump ilə açın. Call stack-lərə baxaraq threadlərin harda ilişdiyini və kim hansı lock-u saxladığını görmək olar.
Stack analiz nümunəsi:
0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
1 000001d4b6f90e08 1 1 000001d4b5c941c0 000001d4b6f03458
(Dump-lara adətən deployment üzrə "təcrübəli jedilər" müraciət edir — qorxmayın, bu güclü alətdir.)
5. Yük altında unit-testlər (stress testing)
Çoxthreadli kodu paralel olaraq yüzlərlə/minlərlə iterasiyada işlədin — nadir yarışlar (race) belə daha asan tapılır.
[Test]
public void Counter_IsThreadSafe()
{
var counter = 0;
var locker = new object();
var tasks = new List<Task>();
for (int i = 0; i < 100; i++)
{
tasks.Add(Task.Run(() =>
{
for (int j = 0; j < 10000; j++)
{
lock (locker)
{
counter++;
}
}
}));
}
Task.WaitAll(tasks.ToArray());
Assert.AreEqual(100 * 10000, counter);
}
6. Asserts və xüsusi yoxlamalardan istifadə
Debug zamanı düzgün vəziyyəti təmin edən yoxlamalar əlavə edin. Məsələn, eyni thread tərəfindən resursun təkrar tutulması cəhdində Debug.Assert.
3. Nəticələr və tövsiyələr
Vizual sxem: təhlükəli zona və təhlükəsizlik
graph TD
A[Ümumi resurs] -- sinxronizasiya olmadan --> B(Vaziyatı yarış)
A -- bloklama (lock/Mutex) --> C[Təhlükəsiz çıxış: kritik bölmə]
C -- "çox lock-lar" --> D(Performans itkisi)
A -- ReaderWriterLockSlim --> E{Çox oxuyan / Bir yazar}
E -- "Oxuma" --> F[Çox thread eyni vaxtda oxuyur]
E -- "Yazma" --> G[Yalnız biri yazır, digərləri gözləyir]
Sinxronizasiya primitivləri və onların məqsədi
| Primitiv | Nə üçün lazım | Neçə thread keçir | Proseslərarası | Performans | Harada istifadə etmək |
|---|---|---|---|---|---|
|
Sadə kritik bölmə | 1 | Xeyr | Çox yüksək | 99% case-lər |
|
Eyni bölmə, amma proseslər arasında | 1 | Bəli | Orta | Files, IPC |
|
Ən çox N thread | N | Bəli | Orta | Resurs pool-ları |
|
Eyni, amma daha sürətlidir, proses daxilində | N | Xeyr | Yüksək | Kodda pool-lar |
|
Çox oxuyanlar, bir yazar | Çox/1 | Xeyr | Yüksək | Cache-lər, konfiqurasiyalar |
GO TO FULL VERSION