CodeGym /Kurslar /C# SELF /Digər problemlər: Livelock və Starvation

Digər problemlər: Livelock və Starvation

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

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.TryEnterThread.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
2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Starvation Demonstrasiyası
Starvation Demonstrasiyası
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION