በደንብ ዚተመገቡ ፈላስፎቜ ወይም ተወዳዳሪ .NET ፕሮግራሚንግ

በደንብ ዚተመገቡ ፈላስፎቜ ወይም ተወዳዳሪ .NET ፕሮግራሚንግ

ዚፈላስፋዎቜ ዚመመገቢያ ቜግርን እንደ ምሳሌ በመጠቀም በኔት ላይ ዚተጣጣመ እና ትይዩ ፕሮግራሚንግ እንዎት እንደሚሰራ እንይ። እቅዱ ይህ ነው, ክሮቜ / ሂደቶቜን ኚማመሳሰል, ወደ ተዋናይ ሞዮል (በሚኚተሉት ክፍሎቜ). ጜሑፉ ለመጀመሪያው ትውውቅ ወይም እውቀትዎን ለማደስ ጠቃሚ ሊሆን ይቜላል።

ለምንድነው በጭራሜ? ትራንዚስተሮቜ በትንሹ መጠናቾው ላይ ይደርሳሉ, ዹሙር ህግ በብርሃን ፍጥነት ገደብ ላይ ዹተመሰሹተ ነው, ስለዚህም በቁጥር መጹመር ይታያል, ብዙ ትራንዚስተሮቜ ሊደሹጉ ይቜላሉ. በተመሳሳይ ጊዜ ዚውሂብ መጠን እያደገ ነው, እና ተጠቃሚዎቜ ኚስርአቶቜ አፋጣኝ ምላሜ ይጠብቃሉ. በእንደዚህ ዓይነት ሁኔታ, "ዹተለመደ" ፕሮግራም, አንድ ዚማስፈጞሚያ ክር ሲኖሚን, ኹአሁን በኋላ ውጀታማ አይደለም. በአንድ ጊዜ ወይም በአንድ ጊዜ ዹአፈፃፀም ቜግርን በሆነ መንገድ መፍታት ያስፈልግዎታል። ኹዚህም በላይ ይህ ቜግር በተለያዩ ደሚጃዎቜ ውስጥ ይገኛል: በክሮቜ ደሹጃ, በሂደት ደሹጃ, በኔትወርክ ውስጥ ባሉ ማሜኖቜ (ዹተኹፋፈሉ ስርዓቶቜ). .NET እንደዚህ አይነት ቜግሮቜን በፍጥነት እና በብቃት ለመፍታት ኹፍተኛ ጥራት ያላ቞ው፣ በጊዜ ዚተሞኚሩ ቎ክኖሎጂዎቜ አሉት።

ዓላማ

Edsger Dijkstra ይህንን ቜግር ለተማሪዎቹ እንደ 1965 አቅርቧል። ዹተቋቋመው ቀመር እንደሚኚተለው ነው። ዹተወሰነ (ብዙውን ጊዜ አምስት) ዚፈላስፎቜ ቁጥር እና ተመሳሳይ ዚሹካዎቜ ብዛት አለ። ክብ ጠሹጮዛ ላይ ተቀምጠዋል, በመካኚላ቞ው ሹካዎቜ. ፈላስፋዎቜ ማለቂያ ዹሌለውን ምግብ ኚሳህና቞ው መብላት፣ ማሰብ ወይም መጠበቅ ይቜላሉ። ፈላስፋን ለመብላት, ሁለት ሹካዎቜን መውሰድ ያስፈልግዎታል (ዚመጚሚሻው ሹካውን ኚመጀመሪያው ጋር ይጋራል). ሹካ ማንሳት እና ማስቀመጥ ሁለት ዚተለያዩ ተግባራት ና቞ው። ሁሉም ፈላስፎቜ ዝም አሉ። ስራው ሁሉም ኹ 54 አመታት በኋላ እንኳን ዚሚያስቡ እና ዚሚሞሉበት እንዲህ አይነት ስልተ-ቀመር ማግኘት ነው.

በመጀመሪያ, ይህንን ቜግር በጋራ ቊታ በመጠቀም ለመፍታት እንሞክር. ሹካዎቹ በጋራ ጠሹጮዛ ላይ ይተኛሉ እና ፈላስፋዎቹ በቀላሉ ሲገኙ ወስደው ይመለሳሉ. እዚህ በማመሳሰል ላይ ቜግሮቜ አሉ፣ መቌ በትክክል surebets መውሰድ? ሹካ ኚሌለስ? ወዘተ ግን መጀመሪያ ፈላስፋዎቹን እንጀምር።

ክሮቜ ለመጀመር, በክር ገንዳ እንጠቀማለን Task.Run ዘዎ፡

var cancelTokenSource = new CancellationTokenSource();
Action<int> create = (i) => RunPhilosopher(i, cancelTokenSource.Token);
for (int i = 0; i < philosophersAmount; i++) 
{
    int icopy = i;
    // ППЌестОть заЎачу в ПчереЎь пула пПтПкПв. МетПЎ RunDeadlock Ме запускаеться 
    // сразу, а жЎет свПегП пПтПка. АсОМхрПММый запуск.
    philosophers[i] = Task.Run(() => create(icopy), cancelTokenSource.Token);
}

ዹክር ገንዳው ዹተነደፈው ክር መፍጠር እና መሰሹዝን ለማመቻ቞ት ነው። ይህ ገንዳ ኚተግባሮቜ ጋር ወሹፋ አለው እና CLR በእነዚህ ስራዎቜ ብዛት ላይ በመመስሚት ክሮቜን ይፈጥራል ወይም ያስወግዳል። ለሁሉም AppDomains አንድ ገንዳ። ይህ ገንዳ ሁልጊዜ ማለት ይቻላል ጥቅም ላይ መዋል አለበት, ምክንያቱም. ክሮቜን በመፍጠር ፣ በመሰሹዝ ፣ ወሚፋዎቻ቞ውን ፣ ወዘተ ዚመሳሰሉትን መጹነቅ አያስፈልግም ። ያለ ገንዳ ይቻላል ፣ ግን ኚዚያ በቀጥታ መጠቀም አለብዎት። Thread, ይህ ዹክርን ቅድሚያ መለወጥ በሚፈልጉበት ጊዜ, ሚዥም ቀዶ ጥገና ሲኖሚን, ለፎሚግራም ክር, ወዘተ ለጉዳዮቜ ጠቃሚ ነው.

በሌላ ቃል, System.Threading.Tasks.Task ክፍል አንድ ነው Thread, ነገር ግን ኹሁሉም አይነት ም቟ት ጋር-ኚሌሎቜ ተግባራት እገዳ በኋላ አንድን ተግባር ዚማሄድ ቜሎታ, ኚተግባሮቜ መመለስ, ምቹ በሆነ ሁኔታ ማቋሚጥ እና ሌሎቜንም. ወዘተ ዚሚሠሩትን ግንባታዎቜ (በተግባር ላይ ዹተመሰሹተ Asynchronous Pattern, syntactic sugar for IO ስራዎቜን ለመጠበቅ) ለመደገፍ ያስፈልጋሉ. ስለዚህ ጉዳይ በኋላ እንነጋገራለን.

CancelationTokenSource ክሩ እራሱን በጥሪው ክር ምልክት ላይ እንዲያቆም እዚህ ያስፈልጋል.

ዚማመሳሰል ጉዳዮቜ

ዚታገዱ ፈላስፎቜ

እሺ፣ ክሮቜ እንዎት እንደሚፈጠሩ እናውቃለን፣ ምሳ ለመብላት እንሞክር፡-

// КтП какОе вОлкО взял. К прОЌеру: 1 1 3 3 - 1й О 3й взялО первые Ўве пары.
private int[] forks = Enumerable.Repeat(0, philosophersAmount).ToArray();

// ТП же, чтП RunPhilosopher()
private void RunDeadlock(int i, CancellationToken token) 
{
    // ЖЎать вОлку, взять её. ЭквОвалеМтМП: 
    // while(true) 
    //     if forks[fork] == 0 
    //          forks[fork] = i+1
    //          break
    //     Thread.Sleep() ОлО Yield() ОлО SpinWait()
    void TakeFork(int fork) =>
        SpinWait.SpinUntil(() => 
            Interlocked.CompareExchange(ref forks[fork], i+1, 0) == 0);

    // Для прПстПты, МП ЌПжМП с Interlocked.Exchange:
    void PutFork(int fork) => forks[fork] = 0;

    while (true)
    {
        TakeFork(Left(i));
        TakeFork(Right(i));
        eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
        PutFork(Left(i));
        PutFork(Right(i));
        Think(i);

        // ЗавершОть рабПту пП-хПрПшеЌу.
        token.ThrowIfCancellationRequested();
    }
}

እዚህ በመጀመሪያ ዚግራውን ሹካ, እና ትክክለኛውን ሹካ ለመውሰድ እንሞክራለን, እና ኚተሰራ, እንበላለን እና እንመልሳ቞ዋለን. አንድ ሹካ መውሰድ አቶሚክ ነው, ማለትም. ሁለት ክሮቜ በአንድ ጊዜ አንድ ጊዜ መውሰድ አይቜሉም (ትክክል አይደለም: ዚመጀመሪያው ሹካው ነፃ እንደሆነ ይነበባል, ሁለተኛው - በጣም, ዚመጀመሪያው ይወስዳል, ሁለተኛው ይወስዳል). ለዚህ Interlocked.CompareExchangeበአቀነባባሪ መመሪያ መተግበር ያለበት (TSL, XCHG)፣ ለአቶሚክ ተኚታታይ ንባብ እና መፃፍ ትውስታን ዚሚቆልፈው። እና SpinWait ኚግንባታው ጋር እኩል ነው። while(true) በትንሜ "አስማት" ብቻ - ክሩ ማቀነባበሪያውን ይወስዳል (Thread.SpinWaitነገር ግን አንዳንድ ጊዜ መቆጣጠሪያን ወደ ሌላ ክር ያስተላልፋል (Thread.Yeildወይም እንቅልፍ መተኛት (Thread.Sleep).

ግን ይህ መፍትሄ አይሰራም, ምክንያቱም ፍሰቶቹ በቅርቡ (ለእኔ በአንድ ሰኚንድ ውስጥ) ታግደዋል፡ ሁሉም ፈላስፎቜ ዚግራ ሹካ቞ውን ይወስዳሉ, ግን ትክክለኛውን አይደለም. ዚሹካዎቹ ድርድር እሎቶቹ አሉት፡ 1 2 3 4 5።

በደንብ ዚተመገቡ ፈላስፎቜ ወይም ተወዳዳሪ .NET ፕሮግራሚንግ

በሥዕሉ ላይ, ዹማገጃ ክሮቜ (deadlock). አሹንጓዮ - ማስፈጞሚያ, ቀይ - ማመሳሰል, ግራጫ - ክር ተኝቷል. ራምቡሶቜ ዚተግባር መጀመሪያ ጊዜን ያመለክታሉ።

ዚፈላስፎቜ ሚሃብ

ምንም እንኳን በተለይ ብዙ ምግብን ማሰብ አስፈላጊ ባይሆንም ሚሃብ ግን ማንም ሰው ፍልስፍናን እንዲተው ያደርገዋል. በቜግራቜን ውስጥ ክሮቜ ዚተራቡበትን ሁኔታ ለመምሰል እንሞክር. ሚሃብ ማለት ክር ሲሮጥ ነው, ነገር ግን ጉልህ ዹሆነ ስራ ኹሌለ, በሌላ አነጋገር, ይህ ተመሳሳይ ዚሞት መቆለፊያ ነው, አሁን ክሩ አይተኛም, ነገር ግን ዹሚበላ ነገርን በንቃት እዚፈለገ ነው, ነገር ግን ምንም ምግብ ዹለም. ተደጋጋሚ እገዳን ለማስቀሚት, ሌላ መውሰድ ካልቻልን ሹካውን እናስቀምጠዋለን.

// ТП же чтП О в RunDeadlock, МП теперь клаЎеЌ вОлку МазаЎ О ЎПбавляеЌ плПхОх фОлПсПфПв.
private void RunStarvation(int i, CancellationToken token)
{
    while (true)
    {
        bool hasTwoForks = false;
        var waitTime = TimeSpan.FromMilliseconds(50);
        // ПлПхПй фОлПсПфПв ЌПжет уже ОЌеть вОлку:
        bool hasLeft = forks[Left(i)] == i + 1;
        if (hasLeft || TakeFork(Left(i), i + 1, waitTime))
        {
            if (TakeFork(Right(i), i + 1, TimeSpan.Zero))
                hasTwoForks = true;
            else
                PutFork(Left(i)); // ИМПгЎа плПхПй фОлПсПф ПтЎает вОлку МазаЎ.
        } 
        if (!hasTwoForks)
        {
            if (token.IsCancellationRequested) break;
            continue;
        }
        eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
        bool goodPhilosopher = i % 2 == 0;
        // А плПхПй фОлПсПф забывает пПлПжОть свПю вОлку ПбратМП:
        if (goodPhilosopher)
            PutFork(Left(i));
        // А еслО О правую Ме пПлПжОт, тП хПрПшОе буЎут вППбще без еЎы.
        PutFork(Right(i));

        Think(i);

        if (token.IsCancellationRequested)
            break;
    }
}

// Теперь ЌПжМП жЎать ПпреЎелеММПе вреЌя.
bool TakeFork(int fork, int philosopher, TimeSpan? waitTime = null)
{
    return SpinWait.SpinUntil(
        () => Interlocked.CompareExchange(ref forks[fork], philosopher, 0) == 0,
              waitTime ?? TimeSpan.FromMilliseconds(-1)
    );
}

በዚህ ኮድ ውስጥ ዋናው ነገር ኚአራቱ ፈላስፋዎቜ ሁለቱ ዚግራ ሹካ቞ውን ማስቀመጥ ይሚሳሉ. እና ተጚማሪ ምግብ እንደሚመገቡ, ሌሎቜ ደግሞ በሚሃብ ይጀምራሉ, ምንም እንኳን ክሮቜ ተመሳሳይ ቅድሚያ ቢኖራ቞ውም. እዚህ ሙሉ በሙሉ ዚተራቡ አይደሉም, ምክንያቱም. መጥፎ ፈላስፎቜ አንዳንድ ጊዜ ሹካዎቻ቞ውን ወደ ኋላ ይመለሳሉ. ጥሩ ሰዎቜ ኚመጥፎ 5 እጥፍ ያነሰ ይበላሉ. ስለዚህ በኮዱ ውስጥ ትንሜ ስህተት ወደ አፈፃፀሙ ውድቀት ይመራል. እዚህ ላይ ደግሞ ሁሉም ፈላስፎቜ ዚግራውን ሹካ ሲወስዱ፣ ቀኝ ዚለም፣ ግራውን ሲያስቀምጡ፣ ሲጠብቁ፣ ግራውን እንደገና ሲወስዱ፣ ወዘተ ያልተለመደ ሁኔታ ሊኚሰት እንደሚቜል ልብ ሊባል ይገባል። ይህ ሁኔታ ደግሞ ሚሃብ ነው, ዹበለጠ እንደ መሞት. ልድገመው አቃተኝ። ኚታቜ ያለው ምስል ሁለት መጥፎ ፈላስፎቜ ሁለቱንም ሹካ ዚወሰዱበት እና ሁለት ጥሩዎቜ በሚሃብ ዚተጠቁበት ሁኔታ ነው.

በደንብ ዚተመገቡ ፈላስፎቜ ወይም ተወዳዳሪ .NET ፕሮግራሚንግ

እዚህ ክሮቹ አንዳንድ ጊዜ ኚእንቅልፍ እንደሚነቁ እና ሀብቱን ለማግኘት እንደሚሞክሩ ማዚት ይቜላሉ. ኚአራቱ ኮር ሁለቱ ምንም አያደርጉም (ኹላይ አሹንጓዮ ግራፍ)።

ዚፈላስፋ ሞት

እንግዲህ፣ ዚፈላስፎቜን ዹኹበሹ እራት ሊያቋርጠው ዚሚቜለው ሌላው ቜግር አንዱ በድንገት በእጁ ሹካ ይዞ ቢሞት (እንደዚያው ይቀብሩታል)። ኚዚያም ጎሚቀቶቜ ያለ ምሳ ይቀራሉ. ለዚህ ጉዳይ እራስዎ ምሳሌ ኮድ ይዘው መምጣት ይቜላሉ, ለምሳሌ, ተጥሏል NullReferenceException ፈላስፋው ሹካዎቹን ኹወሰደ በኋላ. እና ፣ በነገራቜን ላይ ፣ ልዩነቱ አይስተናገድም እና ዚጥሪ ኮድ ብቻ አይይዘውም (ለዚህ AppDomain.CurrentDomain.UnhandledException እና ወዘተ)። ስለዚህ ዚስህተት ተቆጣጣሪዎቜ በራሳ቞ው ክሮቜ ውስጥ እና በሚያምር ማቋሚጫ ያስፈልጋሉ።

አስተናጋጁ

እሺ፣ ይህንን ዚመዝጋት፣ ዚሚሃብ እና ዚሞት ቜግር እንዎት እንፈታዋለን? ሹካዎቹ ላይ አንድ ፈላስፋ ብቻ እንዲደርስ እንፈቅዳለን ፣ለዚህ ቊታ ክሮቜ ዚጋራ መገለልን እንጚምር። እንዎት ማድሚግ ይቻላል? አንድ አስተናጋጅ ሹካዎቹን እንዲወስድ ለማንኛውም ፈላስፋ ፈቃድ ኹሰጠው ፈላስፋዎቹ አጠገብ ቆሞ እንበል። ይህንን አገልጋይ እንዎት እንደምናደርገው እና ​​ፈላስፋዎቜ እንዎት እንደሚጠይቁት, ጥያቄዎቹ አስደሳቜ ናቾው.

በጣም ቀላሉ መንገድ ፈላስፋዎቹ በቀላሉ ሹካዎቹ እንዲደርሱበት አስተናጋጁን ሁልጊዜ ሲጠይቁ ነው። እነዚያ። አሁን ፈላስፋዎቜ በአቅራቢያው ያለውን ሹካ አይጠብቁም ፣ ግን ይጠብቁ ወይም አስተናጋጁን ይጠይቁ። መጀመሪያ ላይ ለዚህ ዹተጠቃሚ ቊታን ብቻ እንጠቀማለን ፣ በእሱ ውስጥ ማንኛውንም ሂደቶቜ ኹኹርነል ለመደወል ማቋሚጥን አንጠቀምም (ኹዚህ በታቜ ስለእነሱ)።

በተጠቃሚ ቊታ ውስጥ መፍትሄዎቜ

እዚህ ጋር በአንድ ሹካ እና ሁለት ፈላስፋዎቜ እንደምናደርገው ተመሳሳይ ነገር እናደርጋለን, በዑደት ውስጥ እንሜኚሚክራለን እና እንጠብቃለን. አሁን ግን ሁሉም ፈላስፎቜ እና እንደነበሩ, አንድ ሹካ ብቻ ይሆናል, ማለትም. ይህን "ወርቃማ ሹካ" ኚአስተናጋጁ ዹወሰደው ፈላስፋ ብቻ ይበላል ማለት ይቻላል. ለዚህም SpinLock እንጠቀማለን.

private static SpinLock spinLock = new SpinLock();  // Наш "ПфОцОаМт"
private void RunSpinLock(int i, CancellationToken token)
{
    while (true)
    {
        // ВзаОЌМая блПкОрПвка через busy waiting. ВызываеЌ ЎП try, чтПбы
        // выбрасОть ОсключеМОе в случае ПшОбкО в саЌПЌ SpinLock.
        bool hasLock = false;
        spinLock.Enter(ref hasLock);
        try
        {
            // ЗЎесь ЌПжет быть тПлькП ПЎОМ пПтПк (mutual exclusion).
            forks[Left(i)] = i + 1;  // БереЌ вОлку сразу, без ПжОЎаМОя.
            forks[Right(i)] = i + 1;
            eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
            forks[Left(i)] = 0;
            forks[Right(i)] = 0;
        }
        finally
        {
            if(hasLock) spinLock.Exit();  // ИзбегаеЌ прПблеЌы сП сЌертью фОлПсПфа.
        }

        Think(i);

        if (token.IsCancellationRequested)
            break;
    }
}

SpinLock ይህ ማገጃ ነው፣ በግምት ሲናገር፣ አንድ ነው። while(true) { if (!lock) break; }ነገር ግን ኚውስጥ ዹበለጠ "አስማት" ያለው SpinWait (እዚያ ጥቅም ላይ ዹሚውለው). አሁን ዚሚጠባበቁትን እንዎት እንደሚቆጥር፣ ትንሜ እንዲተኙ እና ሌሎቜንም ያውቃል። ወዘተ በአጠቃላይ, ለማመቻ቞ት ዚተቻለውን ሁሉ ያደርጋል. ነገር ግን ይህ አሁንም ያው ዚአቀነባባሪ ሃብቶቜን ዹሚበላ እና ፍሰቱን ዚሚጠብቅ ያው ንቁ ዑደት መሆኑን ማስታወስ አለብን ይህም ኚፈላስፋዎቹ አንዱ ኚሌሎቜ ዹበለጠ ቅድሚያ ዹሚሰጠው ኹሆነ ግን ወርቃማ ሹካ (ቅድሚያ ግልበጣ ቜግር) ኹሌለው ሚሃብን ያስኚትላል። . ስለዚህ እኛ ዹምንጠቀመው ለጋራ ማህደሹ ትውስታ በጣም አጭር ለውጊቜ ብቻ ነው፣ ያለ ምንም ዚሶስተኛ ወገን ጥሪ፣ ዹጎጆ መቆለፊያ እና ሌሎቜ አስገራሚ ነገሮቜ።

በደንብ ዚተመገቡ ፈላስፎቜ ወይም ተወዳዳሪ .NET ፕሮግራሚንግ

ሥዕል ለ SpinLock. ጅሚቶቜ ለወርቃማው ሹካ ያለማቋሚጥ "ይዋጋሉ". ውድቀቶቜ አሉ - በሥዕሉ ላይ, ዹተመሹጠው ቊታ. ኮርሶቹ ሙሉ በሙሉ ጥቅም ላይ አልዋሉም: በእነዚህ አራት ክሮቜ 2/3 ያህል ብቻ።

እዚህ ሌላ መፍትሔ ብቻ መጠቀም ይሆናል Interlocked.CompareExchange ኹላይ ባለው ኮድ (በተራቡ ፈላስፋዎቜ) ላይ እንደሚታዚው በተመሳሳይ ንቁ ጥበቃ ፣ ግን ይህ ፣ ቀደም ሲል እንደተናገሚው ፣ በንድፈ-ሀሳብ ወደ ማገድ ሊያመራ ይቜላል።

ላይ Interlocked ብቻ እንዳልሆነ ልብ ሊባል ይገባል። CompareExchange፣ ግን ለአቶሚክ ንባብ እና ለመፃፍ ሌሎቜ ዘዎዎቜ። እና በለውጡ መደጋገም ፣ሌላ ክር ለውጊቹን ለማድሚግ ጊዜ ቢኖሚው (1 አንብብ ፣ 2 አንብብ ፣ 2 ፃፍ ፣ 1 ፃፍ መጥፎ ነው) ለአንድ እሎት ውስብስብ ለውጊቜን መጠቀም ይቻላል (ዹተጠላለፈ ማንኛውም ነገር ንድፍ) .

ዹኹርነል ሁነታ መፍትሄዎቜ

በ loop ውስጥ ሀብትን ላለማባኚን፣ ክርን እንዎት እንደምናገድብ እንይ። በሌላ አነጋገር፣ ምሳሌአቜንን በመቀጠል፣ አስተናጋጁ ፈላስፋውን እንዎት እንደሚያንቀላፋ እና አስፈላጊ ሲሆን ብቻ እንደሚያስነሳው እንመልኚት። በመጀመሪያ ፣ ይህንን በስርዓተ ክወናው ዹኹርነል ሁኔታ እንዎት እንደምናደርግ እንመልኚት ። እዚያ ያሉት ሁሉም መዋቅሮቜ በተጠቃሚ ቊታ ውስጥ ካሉት ይልቅ ቀርፋፋ ና቞ው። ብዙ ጊዜ ቀርፋፋ፣ ለምሳሌ AutoResetEvent ምናልባት 53 ጊዜ ቀርፋፋ SpinLock [ሪቜተር]። ነገር ግን በእነሱ እርዳታ በስርዓቱ ውስጥ ያሉትን ሂደቶቜ ማመሳሰል ይቜላሉ, ዚሚተዳደርም ሆነ አይደለም.

እዚህ ያለው መሠሚታዊ ግንባታ ኚግማሜ ምዕተ ዓመት በፊት በዲጅክስታራ ዹቀሹበው ሮማፎር ነው። ሮማፎር በቀላል አነጋገር በስርዓቱ ዚሚተዳደር አዎንታዊ ኢንቲጀር እና በላዩ ላይ ሁለት ክዋኔዎቜ መጹመር እና መቀነስ ና቞ው። መቀነስ ካልተሳካ, ዜሮ, ኚዚያም ዚጥሪው ክር ታግዷል. ቁጥሩ በሌላ ገባሪ ክር/ሂደት ሲጚመር ክሩቹ ተዘለዋል እና ሎማፎሩ እንደገና በተላለፈው ቁጥር ይቀንሳል። ባቡሮቜን በጠርሙስ አንገት ኹሮማፎር ጋር መገመት ይቻላል። .NET ተመሳሳይ ተግባር ያላ቞ው በርካታ ግንባታዎቜን ያቀርባል፡- AutoResetEvent, ManualResetEvent, Mutex እና እኔ ራሎ Semaphore. እንጠቀማለን AutoResetEventይህ ኚእነዚህ ግንባታዎቜ ውስጥ በጣም ቀላሉ ነው-ሁለት እሎቶቜ 0 እና 1 (ውሞት ፣ እውነት) ብቻ። ዚእሷ ዘዮ WaitOne() እሎቱ 0 ኹሆነ ዚጥሪውን ክር ያግዳል፣ እና 1 ኚሆነ፣ ወደ 0 ዝቅ አድርገው ይዘለላሉ። ዘዮ Set() ወደ 1 ኹፍ ብሎ አንድ አስተናጋጅ እንዲያልፍ ያስቜለዋል፣ እሱም እንደገና ወደ 0 ዝቅ ብሎ። እንደ ዚምድር ውስጥ ባቡር መታጠፊያ ነው።

መፍትሄውን እናወሳስበው እና መቆለፊያውን ለእያንዳንዱ ፈላስፋ እንጠቀም እንጂ ለሁሉም በአንድ ጊዜ አይደለም። እነዚያ። አሁን ብዙ ፈላስፎቜ በአንድ ጊዜ ሊኖሩ ይቜላሉ, እና አንድ አይደሉም. ነገር ግን ኹዘር (ዹዘር ሁኔታዎቜ) መራቅን፣ እርግጠኛ ለመሆን ዚሠንጠሚዡን መዳሚሻ እንደገና እንኚለክላለን።

// Для блПкОрПваМОя ПтЎельМПгП фОлПсПфа.
// ИМОцОалОзОруется: new AutoResetEvent(true) Ўля кажЎПгП.
private AutoResetEvent[] philosopherEvents;

// Для ЎПступа к вОлкаЌ / ЎПступ к стПлу.
private AutoResetEvent tableEvent = new AutoResetEvent(true);

// РПжЎеМОе фОлПсПфа.
public void Run(int i, CancellationToken token)
{
    while (true)
    {
        TakeForks(i); // ЖЎет вОлкО.
        // ОбеЎ. МПжет быть О ЎПльше.
        eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
        PutForks(i); // ОтЎать вОлкО О разблПкОрПвать сПсеЎей.
        Think(i);
        if (token.IsCancellationRequested) break;
    }
}

// ОжОЎать вОлкО в блПкОрПвке.
void TakeForks(int i)
{
    bool hasForks = false;
    while (!hasForks) // ППпрПбПвать еще раз (блПкОрПвка Ме зЎесь).
    {
        // ИсключающОй ЎПступ к стПлу, без гПМПк за вОлкаЌО.
        tableEvent.WaitOne();
        if (forks[Left(i)] == 0 && forks[Right(i)] == 0)
            forks[Left(i)] = forks[Right(i)] = i + 1;
        hasForks = forks[Left(i)] == i + 1 && forks[Right(i)] == i + 1;
        if (hasForks)
            // Теперь фОлПсПф пПест, выйЎет Оз цОкла. ЕслО Set 
            // вызваМ ЎважЎы, тП зМачеМОе true.
            philosopherEvents[i].Set();
        // РазблПкОрПвать ПЎМПгП ПжОЎающегП. ППсле МегП зМачеМОе tableEvent в false.
        tableEvent.Set(); 
        // ЕслО ОЌеет true, Ме блПкОруется, а еслО false, тП буЎет жЎать Set Пт сПсеЎа.
        philosopherEvents[i].WaitOne();
    }
}

// ОтЎать вОлкО О разблПкОрПвать сПсеЎей.
void PutForks(int i)
{
    tableEvent.WaitOne(); // Без гПМПк за вОлкаЌО.
    forks[Left(i)] = 0;
    // ПрПбуЎОть левПгП, а пПтПЌ О правПгП сПсеЎа, лОбП AutoResetEvent в true.
    philosopherEvents[LeftPhilosopher(i)].Set();
    forks[Right(i)] = 0;
    philosopherEvents[RightPhilosopher(i)].Set();
    tableEvent.Set();
}

እዚህ ምን እዚተፈጠሚ እንዳለ ለመሚዳት ፈላስፋው ሹካዎቜን መውሰድ ሲያቅተው ጉዳዩን አስቡበት, ኚዚያም ድርጊቱ እንደሚኚተለው ይሆናል. ወደ ጠሹጮዛው ለመድሚስ እዚጠበቀ ነው. ኹተቀበለ በኋላ ሹካዎቹን ለመውሰድ ይሞክራል. አልሰራም። ወደ ጠሹጮዛው (ዚጋራ መገለል) መዳሚሻ ይሰጣል. እና ዚእሱን "ማዞሪያ" ያልፋል (AutoResetEvent) (በመጀመሪያ ክፍት ናቾው). እንደገና ወደ ዑደት ውስጥ ይገባል, ምክንያቱም ሹካዎቜ ዚሉትም። ሊወስዳ቞ው ይሞክራል እና በእሱ "መታጠፊያ" ላይ ይቆማል. በቀኝም በግራም አንዳንድ እድለኛ ጎሚቀቶቜ በልተው እንደጚሚሱ ፈላስፋቜንን “መታጠፊያውን ኚፍቶ” ኚፍተውታል። ፈላስፋቜን አልፏል (ኹኋላውም ይዘጋል) ለሁለተኛ ጊዜ። ሹካዎቹን ለመውሰድ ለሊስተኛ ጊዜ ይሞክራል። መልካም ምኞት. እና ለመመገብ መዞሪያውን ያልፋል።

በእንደዚህ ዓይነት ኮድ ውስጥ ዹዘፈቀደ ስህተቶቜ ሲኖሩ (ሁልጊዜም ይኖራሉ) ለምሳሌ ጎሚቀት በስህተት ይገለጻል ወይም ተመሳሳይ ነገር ይፈጠራል። AutoResetEvent ለሁሉም (Enumerable.Repeat), ኚዚያ ፈላስፋዎቹ ገንቢዎቜን ይጠብቃሉ, ምክንያቱም በእንደዚህ ዓይነት ኮድ ውስጥ ስህተቶቜን መፈለግ በጣም ኚባድ ስራ ነው። ሌላው ዹዚህ መፍትሔ ቜግር አንዳንድ ፈላስፋዎቜ እንዳይራቡ ዋስትና አለመስጠቱ ነው።

ድብልቅ መፍትሄዎቜ

በተጠቃሚ ሁነታ እና ሉፕ ውስጥ ስንቆይ እና በኹርነል ውስጥ ክር ስንዘጋ ሁለት አቀራሚቊቜን ተመልክተናል። ዚመጀመሪያው ዘዮ ለአጭር መቆለፊያዎቜ ጥሩ ነው, ሁለተኛው ደግሞ ለሹጅም ጊዜ ነው. ብዙውን ጊዜ በመጀመሪያ አንድ ተለዋዋጭ በ loop ውስጥ እስኪቀዚር ድሚስ ለአጭር ጊዜ መጠበቅ እና ጥበቃው ሹጅም በሚሆንበት ጊዜ ክርውን ማገድ ያስፈልጋል። ይህ አቀራሚብ በሚባሉት ውስጥ ተተግብሯል. ድብልቅ መዋቅሮቜ. ኹኹርነል ሁነታ ጋር ተመሳሳይ ግንባታዎቜ እዚህ አሉ፣ አሁን ግን በተጠቃሚ ሁነታ ዑደትፊ SemaphorSlim, ManualResetEventSlim ወዘተ እዚህ በጣም ታዋቂው ንድፍ ነው Monitor, ምክንያቱም በ C # ውስጥ በጣም ዚታወቀ ነገር አለ lock አገባብ። Monitor ይህ ኹፍተኛው 1 (mutex) ያለው ተመሳሳይ ሮማፎር ነው፣ ነገር ግን በ loop ውስጥ ለመጠበቅ ድጋፍ ፣ ተደጋጋሚነት ፣ ዚሁኔታ ተለዋዋጭ ንድፍ (ኹዚህ በታቜ ዹበለጠ) ፣ ወዘተ. ኚእሱ ጋር አንድ መፍትሄ እንይ።

// СпрячеЌ Пбъект Ўля МПМОтПра Пт всех, чтПбы без ЎеЎлПкПв.
private readonly object _lock = new object();
// ВреЌя ПжОЎаМОя пПтПка.
private DateTime?[] _waitTimes = new DateTime?[philosophersAmount];

public void Run(int i, CancellationToken token)
{
    while (true)
    {
        TakeForks(i);
        eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
        PutForks(i);
        Think(i);
        if (token.IsCancellationRequested) break;
    }
}

// Наше слПжМПе услПвОе Ўля Condition Variable паттерМа.
bool CanIEat(int i)
{
    // ЕслО есть вОлкО:
    if (forks[Left(i)] != 0 && forks[Right(i)] != 0)
        return false;
    var now = DateTime.Now;
    // МПжет, еслО сПсеЎО Ме бПлее гПлПЎМые, чеЌ текущОй.
    foreach(var p in new int[] {LeftPhilosopher(i), RightPhilosopher(i)})
        if (_waitTimes[p] != null && now - _waitTimes[p] > now - _waitTimes[i])
            return false;
    return true;
}

void TakeForks(int i)
{
    // ЗайтО в МПМОтПр. ТП же саЌПе: lock(_lock) {..}.
    // ВызываеЌ вМе try, чтПбы вПзЌПжМПе ОсключеМОе выбрасывалПсь выше.
    bool lockTaken = false;
    Monitor.Enter(_lock, ref lockTaken);
    try
    {
        _waitTimes[i] = DateTime.Now;
        // Condition Variable паттерМ. ОсвПбПжЎаеЌ лПк, еслО Ме выпПлМеММП 
        // слПжМПе услПвОе. И жЎеЌ пПка ктП-МОбуЎь сЎелает Pulse / PulseAll.
        while (!CanIEat(i))
            Monitor.Wait(_lock); 
        forks[Left(i)] = i + 1;
        forks[Right(i)] = i + 1;
        _waitTimes[i] = null;
    }
    finally
    {
        if (lockTaken) Monitor.Exit(_lock);
    }
}

void PutForks(int i)
{
    // ТП же саЌПе: lock (_lock) {..}.
    bool lockTaken = false;
    Monitor.Enter(_lock, ref lockTaken);
    try
    {
        forks[Left(i)] = 0;
        forks[Right(i)] = 0;
        // ОсвПбПЎОть все пПтПкО в ПчереЎО ПОСЛЕ вызПва Monitor.Exit.
        Monitor.PulseAll(_lock); 
    }
    finally
    {
        if (lockTaken) Monitor.Exit(_lock);
    }
}

እዚህ እንደገና ወደ ሹካዎቜ ለመድሚስ ሙሉውን ጠሹጮዛ እዚዘጋን ነው, አሁን ግን ሁሉንም ክሮቜ በአንድ ጊዜ እንኚፍታለን, እና አንድ ሰው በልቶ ሲጚርስ ጎሚቀቶቜ አይደለም. እነዚያ። በመጀመሪያ አንድ ሰው ይበላል እና ጎሚቀቶቹን ያግዳል, እና ይህ ሰው ሲጚርስ, ነገር ግን እንደገና መብላት ሲፈልግ, ወደ ማገድ ሄዶ ጎሚቀቶቹን ያስነሳል, ምክንያቱም. ዚእሱ ጥበቃ ጊዜ ያነሰ ነው.

በዚህ መንገድ ነው ኚሞት መዘጋት እና ዚአንዳንድ ፈላስፋዎቜን ሚሃብ ዚምናስወግደው። ለአጭር ጊዜ ለመጠበቅ አንድ loop እንጠቀማለን እና ክርውን ለሹጅም ጊዜ እንዘጋዋለን. ሁሉንም ሰው በአንድ ጊዜ ማንሳት ጎሚቀት ብቻ ኚመታገድ ይልቅ ቀርፋፋ ነው፣ እንደ መፍትሄው AutoResetEvent, ግን ልዩነቱ ትልቅ መሆን ዚለበትም, ምክንያቱም ክሮቜ መጀመሪያ በተጠቃሚ ሁኔታ ውስጥ መቆዚት አለባ቞ው።

У lock አገባብ አስቀያሚ አስገራሚ ነገሮቜ አሉት። ለመጠቀም ይመኚራል Monitor በቀጥታ [Richter] [Eric Lippert]. ኚመካኚላ቞ው አንዱ ያ ነው። lock ሁልጊዜ ውጪ Monitorምንም እንኳን ዹተለዹ ነገር ቢኖርም ፣ በዚህ ሁኔታ ውስጥ ሌላ ክር ዚጋራ ማህደሹ ትውስታ ሁኔታን ሊለውጥ ይቜላል። በእንደዚህ ዓይነት ሁኔታዎቜ ብዙውን ጊዜ ወደ መቆለፊያ መሄድ ወይም በሆነ መንገድ ፕሮግራሙን ማቋሚጥ ይሻላል። ሌላው ዚሚያስደንቀው ሞኒተር ዚማመሳሰል ብሎኮቜን ይጠቀማል (SyncBlock), በሁሉም ነገሮቜ ውስጥ ዚሚገኙት. ስለዚህ፣ አግባብ ያልሆነ ነገር ኹተመሹጠ በቀላሉ መቆለፊያ (ለምሳሌ፣ ዹተጠላለፈ ገመድ ላይ ኹቆለፉ) በቀላሉ ማግኘት ይቜላሉ። ለዚህ ሁልጊዜ ዹተደበቀውን ነገር እንጠቀማለን.

ዚሁኔታ ተለዋዋጭ ስርዓተ-ጥለት ዚአንዳንድ ውስብስብ ሁኔታዎቜን መጠበቅ በትክክል እንዲተገብሩ ያስቜልዎታል። በ NET ውስጥ, በእኔ አስተያዚት, ያልተሟላ ነው, ምክንያቱም በንድፈ ሀሳብ ፣ በብዙ ተለዋዋጮቜ ላይ ብዙ ወሚፋዎቜ ሊኖሩ ይገባል (እንደ ፖዚክስ ክሮቜ) ፣ እና በአንድ ሎክ ላይ። ኚዚያም አንድ ሰው ለሁሉም ፈላስፎቜ ሊያደርጋ቞ው ይቜላል. ነገር ግን በዚህ ቅጜ ውስጥ እንኳን, ኮዱን እንዲቀንሱ ያስቜልዎታል.

ብዙ ፈላስፎቜ ወይም async / await

እሺ፣ አሁን ክሮቜ በብቃት ማገድ እንቜላለን። ግን ብዙ ፈላስፎቜ ቢኖሩን? 100? 10000? ለምሳሌ፣ ለድር አገልጋይ 100000 ጥያቄዎቜ ደርሰውናል። ለእያንዳንዱ ጥያቄ ክር ለመፍጠር ኹላይ ይሆናል, ምክንያቱም ብዙ ክሮቜ በትይዩ አይሄዱም። አመክንዮአዊ ኮሮቜ እንዳሉ ያህል ብቻ ነው ዚሚሰራው (4 አለኝ)። እና ሁሉም ሰው ሀብትን ብቻ ይወስዳል። ለዚህ ቜግር አንዱ መፍትሔ ዚማመሳሰል/ዚመጠባበቅ ጥለት ነው። ዚእሱ ሀሳብ አንድ ነገር እንዲቀጥል መጠበቅ ካስፈለገ ተግባሩ ክር አይይዝም. እና አንድ ነገር ሲያደርግ አፈፃፀሙን ይቀጥላል (ነገር ግን ዚግድ በተመሳሳይ ክር ላይ አይደለም!) በእኛ ሁኔታ, ሹካውን እንጠብቃለን.

SemaphoreSlim ለዚህ አለው WaitAsync() ዘዮ. ይህንን ስርዓተ-ጥለት በመጠቀም ትግበራ እዚህ አለ።

// Запуск такПй же, как раМьше. ГЎе-МОбуЎь в прПграЌЌе:
Task.Run(() => Run(i, cancelTokenSource.Token));

// Запуск фОлПсПфа.
// КлючевПе слПвП async -- кПЌпОлятПр траМслОрует этПт ЌетПт в асОМхрПММый.
public async Task Run(int i, CancellationToken token)
{
    while (true)
    {
        // await -- буЎеЌ ПжОЎать какПгП-тП сПбытОя.
        await TakeForks(i);
        // ППсле await, прПЎПлжеМОе вПзЌПжМП в ЎругПЌ пПтПке.
        eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
        // МПжет быть МескПлькП сПбытОй Ўля ПжОЎаМОя.
        await PutForks(i);

        Think(i);

        if (token.IsCancellationRequested) break;
    }
}

async Task TakeForks(int i)
{
    bool hasForks = false;
    while (!hasForks)
    {
        // ВзаОЌПОсключающОй ЎПступ к стПлу:
        await _tableSemaphore.WaitAsync();
        if (forks[Left(i)] == 0 && forks[Right(i)] == 0)
        {
            forks[Left(i)] = i+1;
            forks[Right(i)] = i+1;
            hasForks = true;
        }
        _tableSemaphore.Release();
        // БуЎеЌ ПжОЎать, чтПбы сПсеЎ пПлПжОл вОлкО:
        if (!hasForks)
            await _philosopherSemaphores[i].WaitAsync();
    }
}

// ЖЎеЌ ЎПступа к стПлу О клаЎеЌ вОлкО.
async Task PutForks(int i)
{
    await _tableSemaphore.WaitAsync();
    forks[Left(i)] = 0;
    // "ПрПбуЎОть" сПсеЎей, еслО ПМО "спалО".
    _philosopherSemaphores[LeftPhilosopher(i)].Release();
    forks[Right(i)] = 0;
    _philosopherSemaphores[RightPhilosopher(i)].Release();
    _tableSemaphore.Release();
}

ዘዮ ኹ async / await ወዲያውኑ ውስጣዊውን ወደ ሚመልስ አስ቞ጋሪ ሁኔታ ማሜን ተተርጉሟል Task. በእሱ በኩል, ዘዮው እስኪጠናቀቅ ድሚስ, መሰሹዝ እና በተግባር ማድሚግ ዚሚቜሉትን ሁሉንም ነገር መጠበቅ ይቜላሉ. በስልቱ ውስጥ ዚስ቎ቱ ማሜን አፈፃፀሙን ይቆጣጠራል. ዋናው ነገር ምንም መዘግዚት ኹሌለ, አፈፃፀሙ ተመሳሳይ ነው, እና ካለ, ኚዚያም ክር ይለቀቃል. ለዚህ ዚተሻለ ግንዛቀ ይህንን ዚስ቎ት ማሜን መመልኚቱ ዚተሻለ ነው. ኚእነዚህ ውስጥ ሰንሰለቶቜን መፍጠር ይቜላሉ async / await ዘዎዎቜ.

እስቲ እንፈትሜ። ዹ100 ፈላስፎቜ ስራ 4 ሎጂካዊ ኮር፣ 8 ሰኚንድ ባለው ማሜን ላይ። ኹMonitor ጋር ዹነበሹው ዹቀደመው መፍትሄ ዚመጀመሪያዎቹን 4 ክሮቜ ብቻ ነው ዹሄደው እና ዹተቀሹው ምንም አልሄደም። እያንዳንዳ቞ው እነዚህ 4 ክሮቜ ለ 2 ሚሰ ያህል ስራ ፈትተዋል። እና ዚአስንክ/ዚመጠባበቅ መፍትሄው 100ቱን ሮጊ ነበር፣በእያንዳንዳ቞ው በአማካይ 6.8 ሰኚንድ እዚጠበቀ። እርግጥ ነው, በእውነተኛ ስርዓቶቜ ውስጥ, ለ 6 ሰኚንድ ስራ ፈትነት ተቀባይነት ዹለውም እና እንደዚህ አይነት ብዙ ጥያቄዎቜን ላለመፈጾም ዚተሻለ ነው. ኹMonitor ጋር ያለው መፍትሄ ጚርሶ ሊሰፋ ዚማይቜል ሆኖ ተገኝቷል።

መደምደሚያ

ኚእነዚህ ትናንሜ ምሳሌዎቜ ማዚት እንደምትቜለው፣ NET ብዙ ዚማመሳሰል ግንባታዎቜን ይደግፋል። ሆኖም ግን, እንዎት እንደሚጠቀሙባ቞ው ሁልጊዜ ግልጜ አይደለም. ይህ ጜሑፍ ጠቃሚ ነበር ብዬ ተስፋ አደርጋለሁ። ለአሁን፣ ይህ መጚሚሻው ነው፣ ነገር ግን አሁንም ብዙ አስደሳቜ ነገሮቜ ቀርተዋል፣ ለምሳሌ፣ ክር-አስተማማኝ ስብስቊቜ፣ TPL Dataflow፣ Reactive programming፣ ዚሶፍትዌር ግብይት ሞዎል፣ ወዘተ።

ምንጮቜ

ምንጭ: hab.com

አስተያዚት ያክሉ