1. Giriş
Livelock — çoxthreadli tətbiqdə iki və ya daha çox thread-in qarşılıqlı bloklanmadan qaçmağa çalışarkən bir-birinin hərəkətlərinə sonsuz reaksiya verib irəli getməməsi vəziyyətidir. Deadlock -dan fərqli olaraq, burada thread-lər donmur və heç nə edə bilmirlər; proqram canlı görünür: thread-lər işləyir, amma heç biri faydalı iş görmür.
Bu, dar koridorda gedən iki həmkarın biri-birinə yol vermək üçün daim geri addım atması və nəticədə heç yerə getməməsi kimidir. Xaricdən gülməlidir, amma proqramda fəlakət ola bilər.
Livelock nümunəsi: proqramçılar qapıda
Təsəvvür edin ki, çox nəzakətli iki proqramçı dar koridorda bir-birilərinə doğru gəlir. Hər ikisi eyni anda qapının qabağında dayanır və hər ikisi yol verməyə qərar verir: kənara çəkilir. Hər ikisi görür ki, vəziyyət dəyişməyib və yenidən yol verir — sonsuz. Heç kim dayanmayıb, hamı hərəkətdədir, amma qapıdan heç kim keçmir.
Çoxthreadli proqramda bu belə görünür: thread resursun tutulduğunu görür, geri çəkilir, yenidən yoxlayır, yenə yol verir — və belə davam edir.
2. Livelock praktikada
Gəlin bu vəziyyəti kodda həyata keçirək. Tutaq ki, iki resursumuz var (məsələn, iki bank hesabı), və iki thread eyni anda bir-birinə pul köçürməyə çalışır, “qarışıqlıq”dan qaçmaq istəyərək.
class Account
{
public int Balance { get; set; }
public object LockObj { get; } = new object();
}
public class Program
{
static void Transfer(Account from, Account to, int amount)
{
while (true)
{
bool lockedFrom = false;
bool lockedTo = false;
try
{
Monitor.TryEnter(from.LockObj, 100, ref lockedFrom);
Monitor.TryEnter(to.LockObj, 100, ref lockedTo);
if (lockedFrom && lockedTo)
{
from.Balance -= amount;
to.Balance += amount;
break; // Köçürmə tamamlandı!
}
}
finally
{
if (lockedFrom) Monitor.Exit(from.LockObj);
if (lockedTo) Monitor.Exit(to.LockObj);
}
// Alınmadı? Geri çəkilib yenidən cəhd edək — nəzakətlə!
Thread.Sleep(1);
}
}
// ... İki thread-i işə salmaq: Transfer(A, B, ...); Transfer(B, A, ...);
}
Bu nümunədə hər iki thread dəfələrlə lock-ları götürməyə çalışır. Amma əgər hər ikisi həmişə lock-ların tutulduğunu görür və yol verirsə, onda hamısı “nəzakətli” yol verməyə çevrilir və pul köçürülmələri baş vermir. Planlayıcının təsadüfinə güvənmək etibarlı strategiya deyil. Pauzalar və backoff əlavə edin, qısa “boş” dövrləri Monitor.TryEnter və Thread.Sleep ilə fırlatmayın.
Livelock və Deadlock müqayisəsi
| Deadlock | Livelock | |
|---|---|---|
| Thread-lər | Dayanıblar, gözləyirlər | Aktiv şəkildə fırlanırlar, gözləyirlər |
| Resurslar | Əbədi bloklanmışdır | Görünən şəkildə bloklanmayıb |
| CPU | Demək olar ki yüklənmir | Bəzən 100%-ə qədər yüklənə bilər |
| Həll yolu | Timeout-lar, lock sıralaması | Təsadüfiyyət, pauzalar, backoff |
3. Starvation (aclıq): resursa növbə çatmır
Starvation (sözün əsl mənasında “aclıq”) — bir və ya bir neçə thread-in daima diqqətdən kənarda qalması və lazım olan resursa daxil ola bilməməsi vəziyyətidir, çünki digər thread-lər həmişə önə keçir.
Deadlock və Livelock-dan fərqli olaraq, burada heç kim əbədi bloklanmır və heç kim sonsuz yol vermir — sadəcə bəzi thread-lərə daim “porsiya” düşmür.
İllustrasiya: real həyat analoqu
Tutaq ki, iki növbəsi olan bufet var: “VIP” (məsələn, işçilər üçün kassası) və “hamı üçün”. VIP-növbə qısa olur, amma ora həmişə kimsə daha tez çatır. “Adi” ziyarətçilər yarım saat növbədə dayanıb VIP-lərin önə keçməsini izləyirlər. Bu Starvation-dır!
4. Starvation C#-da: praktika və debugging
Məsələn, lock ilə işləyərkən bir thread yüksək prioritetə malikdirsə və daima kritik bölməyə daxil olursa, digər thread “geri qalır”:
private static readonly object _locker = new object();
static void Greedy()
{
while (true)
{
lock (_locker)
{
Console.WriteLine("Açgözlü thread resursu ələ keçirdi...");
Thread.Sleep(10); // lock-u daha uzun saxlayır
}
Thread.Sleep(1);
}
}
static void Poor()
{
while (true)
{
lock (_locker)
{
Console.WriteLine("Yoxsul thread daxil olmağa cəhd etdi...");
}
}
}
Hər iki thread işlədikdə, "Greedy" (Açgözlü) uzun müddət lock-u saxlayır və tez-tez onu yenidən götürür, "Poor" (Yoxsul) isə demək olar ki içəri daxil ola bilmir və resursdan “ac qalır”. Real dünyada starvation daha az aşkar ola bilər: məsələn, bir thread-in prioriteti aşağıdır və ya OS onu daha tez preempt edir.
Starvation harada yaranır?
- Aşağı prioritetli thread-lərdə.
- Tapşırıq növbələrinin səhv təşkilində (ədalətli FIFO siyasəti yoxdur).
- ReaderWriterLockSlim -də defolt parametrlər: əgər yazıcı nadir gəlirsə, amma oxucular davamlı gəlibsə, yazıcı daim “ac qalacaq”, çünki oxucular Read Lock götürür və Write Lock almağa imkan vermirlər.
5. Niyə Starvation həmişə səhv deyil, amma problemli olur
Starvation Deadlock -a oxşamır, proqramın tam dayanmasına gətirmir, amma nəticələr qeyri-müəyyən və ədalətsiz olur: məlumatlar bərabər şəkildə emal olunmur, bəzi tapşırıqlar sonsuz gözləyə bilər, performans azalır. Server tətbiqlərində bu xüsusilə kritikdir — hamının bərabər şansı olmalıdır.
Livelock və Starvation necə tapmaq və diaqnoz etmək
- CPU istifadəsi kəskin artıb, amma iş dayanıb — mümkün Livelock.
- Thread-lər işləyir kimi görünür, amma bəzi tapşırıqlar heç vaxt bitmir — Starvation ehtimalı var.
- Loglar: əgər jurnal göstərirsə ki, müəyyən thread və ya tapşırıqlar resursa heç vaxt daxil olmur — 100% starvation.
Livelock-dan necə qaçmaq
- Monitor.TryEnter kimi “münasibətcil” non-blocking metodlarla məhdudlaşmayın. Əgər alınmadısa, təsadüfi pauzalar əlavə edin (Thread.Sleep(Random.Next(1, 10)))), ki, thread-lər eyni zamanda toqquşmasın.
- Davamlı qısa dövrlərdə resurs tutmağa çalışmayın — yoxsa “geri çəkildi — cəhd etdi” dövrü sonsuz ola bilər.
- Bəzən alqoritmi dəyişmək kömək edir: “yaşlıya üstünlük” mexanizmi əlavə edin ki, yalnız biri yol versin, digəri yox.
Starvation-dan necə qorunmaq
- Thread prioritetlərinə nəzarət edin: fon və yüksək prioritetli tapşırıqlar digərinə daima mane olmamalıdır.
- Ədalətli növbələrdən istifadə edin (ConcurrentQueue<T>, kanal strukturları, task queue) və FIFO siyasəti tətbiq edin ki, tapşırıqlar növbə ilə xidmət olunsun.
- ReaderWriterLockSlim -də yeni oxucuları saxlayıb yazıcılara üstünlük vermək olar ki, yazıcı Write Lock alması üçün gözləsin.
- Timeout-lar və logging tətbiq edin: əgər thread çox uzun müddət lock üçün gözləyirsə, xəbərdarlıq verin.
- Lock-ları daha qısa müddətə saxlayın və kritik bölgələri mümkün qədər kiçildin.
Deadlock, Livelock və Starvation müqayisəsi
| Ssenari | Deadlock | Livelock | Starvation |
|---|---|---|---|
| Thread-lər | Ölü vəziyyətdə dayanıblar | Faydasız şəkildə fırlanırlar | Bəziləri işləyir, bəziləri yox |
| Resursa giriş | Yoxdur | Yoxdur | Qeyri-bərabər, bəzən yoxdur |
| CPU | Yüklənmir | Çox yüklənir | Yüklənir, amma hamı tərəfindən deyil |
| Kritik səhv? | Bəli | Bəli | Bəzən |
GO TO FULL VERSION