நன்கு ஊட்டப்பட்ட தத்துவவாதிகள் அல்லது போட்டி .NET புரோகிராமிங்

நன்கு ஊட்டப்பட்ட தத்துவவாதிகள் அல்லது போட்டி .NET புரோகிராமிங்

Philosophers Dining Problemஐ உதாரணமாகக் கொண்டு .Net இல் ஒரே நேரத்தில் மற்றும் இணையான நிரலாக்கம் எவ்வாறு செயல்படுகிறது என்பதைப் பார்ப்போம். இழைகள் / செயல்முறைகளின் ஒத்திசைவு முதல் நடிகர் மாதிரி வரை (பின்வரும் பகுதிகளில்) திட்டம் இதுதான். முதல் அறிமுகம் அல்லது உங்கள் அறிவைப் புதுப்பிக்க கட்டுரை பயனுள்ளதாக இருக்கும்.

அதை ஏன் செய்ய வேண்டும்? டிரான்சிஸ்டர்கள் அவற்றின் குறைந்தபட்ச அளவை அடைகின்றன, மூரின் விதி ஒளியின் வேகத்தின் வரம்பில் உள்ளது, எனவே எண்ணிக்கையில் அதிகரிப்பு காணப்படுகிறது, மேலும் டிரான்சிஸ்டர்களை உருவாக்க முடியும். அதே நேரத்தில், தரவுகளின் அளவு அதிகரித்து வருகிறது, மேலும் பயனர்கள் கணினியிலிருந்து உடனடி பதிலை எதிர்பார்க்கிறார்கள். அத்தகைய சூழ்நிலையில், "சாதாரண" நிரலாக்கமானது, எங்களிடம் ஒரு நூலை இயக்கும் போது, ​​இனி பயனுள்ளதாக இருக்காது. ஒரே நேரத்தில் அல்லது ஒரே நேரத்தில் செயல்படுத்தும் சிக்கலை நீங்கள் எப்படியாவது தீர்க்க வேண்டும். மேலும், இந்த சிக்கல் வெவ்வேறு நிலைகளில் உள்ளது: நூல்களின் மட்டத்தில், செயல்முறைகளின் மட்டத்தில், நெட்வொர்க்கில் உள்ள இயந்திரங்களின் மட்டத்தில் (விநியோகிக்கப்பட்ட அமைப்புகள்). .NET ஆனது, இது போன்ற பிரச்சனைகளை விரைவாகவும் திறமையாகவும் தீர்ப்பதற்கு உயர்தர, நேர-சோதனை செய்யப்பட்ட தொழில்நுட்பங்களைக் கொண்டுள்ளது.

பணி

Edsger Dijkstra 1965 ஆம் ஆண்டிலேயே தனது மாணவர்களுக்கு இந்தப் பிரச்சனையை முன்வைத்தார். நிறுவப்பட்ட உருவாக்கம் பின்வருமாறு. ஒரு குறிப்பிட்ட (பொதுவாக ஐந்து) தத்துவவாதிகள் மற்றும் அதே எண்ணிக்கையிலான முட்கரண்டிகள் உள்ளன. அவர்கள் ஒரு வட்ட மேசையில் அமர்ந்திருக்கிறார்கள், அவர்களுக்கு இடையே முட்கரண்டி. தத்துவஞானிகள் முடிவில்லாத உணவை தங்கள் தட்டுகளிலிருந்து சாப்பிடலாம், சிந்திக்கலாம் அல்லது காத்திருக்கலாம். ஒரு தத்துவஞானியை சாப்பிட, நீங்கள் இரண்டு முட்கரண்டிகளை எடுக்க வேண்டும் (கடைசியானது முதல் முட்கரண்டியுடன் பகிர்ந்து கொள்கிறது). ஒரு முட்கரண்டியை எடுப்பதும் கீழே வைப்பதும் இரண்டு தனித்தனி செயல்கள். அனைத்து தத்துவவாதிகளும் அமைதியாக இருக்கிறார்கள். 54 ஆண்டுகளுக்குப் பிறகும் அவர்கள் அனைவரும் சிந்தித்து முழுமையடையக்கூடிய அத்தகைய வழிமுறையைக் கண்டுபிடிப்பதே பணி.

முதலில், பகிரப்பட்ட இடத்தைப் பயன்படுத்துவதன் மூலம் இந்த சிக்கலை தீர்க்க முயற்சிப்போம். முட்கரண்டிகள் பொதுவான மேசையில் கிடக்கின்றன, தத்துவவாதிகள் அவை இருக்கும்போது அவற்றை எடுத்து மீண்டும் வைக்கிறார்கள். இங்கே ஒத்திசைப்பதில் சிக்கல்கள் உள்ளன, எப்போது சரியாக உறுதிசெய்ய வேண்டும்? முட்கரண்டி இல்லை என்றால் என்ன? முதலியன ஆனால் முதலில், தத்துவவாதிகளை ஆரம்பிக்கலாம்.

இழைகளைத் தொடங்க, நாங்கள் ஒரு நூல் குளத்தைப் பயன்படுத்துகிறோம் 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, ஆனால் அனைத்து வகையான வசதிகளுடன்: மற்ற பணிகளின் தொகுதிக்குப் பிறகு ஒரு பணியை இயக்கும் திறன், செயல்பாடுகளிலிருந்து அவற்றைத் திரும்பப் பெறுதல், வசதியாக குறுக்கிடுதல் மற்றும் பல. போன்றவை. ஒத்திசைவு / காத்திருப்பு கட்டுமானங்களை ஆதரிக்க அவை தேவைப்படுகின்றன (பணி அடிப்படையிலான ஒத்திசைவற்ற முறை, 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 புரோகிராமிங்

படத்தில், தடுக்கும் இழைகள் (முட்டுக்கட்டை). பச்சை - மரணதண்டனை, சிவப்பு - ஒத்திசைவு, சாம்பல் - நூல் தூங்குகிறது. ரோம்பஸ்கள் பணிகளின் தொடக்க நேரத்தைக் குறிக்கின்றன.

தத்துவஞானிகளின் பசி

குறிப்பாக உணவை அதிகம் சிந்திக்க வேண்டிய அவசியமில்லை என்றாலும், பசி யாரையும் தத்துவத்தை கைவிட வைக்கிறது. எங்கள் பிரச்சினையில் நூல்கள் பட்டினி கிடக்கும் சூழ்நிலையை உருவகப்படுத்த முயற்சிப்போம். பட்டினி என்பது ஒரு நூல் இயங்கும், ஆனால் குறிப்பிடத்தக்க வேலை இல்லாமல், வேறுவிதமாகக் கூறினால், இது அதே முட்டுக்கட்டை, இப்போது நூல் தூங்கவில்லை, ஆனால் சாப்பிட ஏதாவது தேடுகிறது, ஆனால் உணவு இல்லை. அடிக்கடி தடுப்பதைத் தவிர்ப்பதற்காக, வேறொன்றை எடுக்க முடியாவிட்டால், முட்கரண்டியை மீண்டும் வைப்போம்.

// То же что и в 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 எழுதவும் மோசமாக உள்ளது), இது ஒரு ஒற்றை மதிப்பில் சிக்கலான மாற்றங்களுக்குப் பயன்படுத்தப்படலாம் (இணைக்கப்பட்ட எதையும் பேட்டர்ன்) .

கர்னல் பயன்முறை தீர்வுகள்

ஒரு சுழற்சியில் வளங்களை வீணாக்குவதைத் தவிர்க்க, ஒரு நூலை எவ்வாறு தடுப்பது என்பதைப் பார்ப்போம். வேறு வார்த்தைகளில் கூறுவதானால், எங்கள் உதாரணத்தைத் தொடர்வதன் மூலம், பணியாளர் எவ்வாறு தத்துவஞானியை தூங்க வைக்கிறார் மற்றும் தேவையான போது மட்டுமே அவரை எழுப்புகிறார் என்று பார்ப்போம். முதலில், இயக்க முறைமையின் கர்னல் பயன்முறையில் இதை எப்படி செய்வது என்று பார்ப்போம். அங்குள்ள அனைத்து கட்டமைப்புகளும் பெரும்பாலும் பயனர் இடத்தில் இருப்பதை விட மெதுவாக இருக்கும். உதாரணமாக, பல மடங்கு மெதுவாக 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), பின்னர் தத்துவவாதிகள் டெவலப்பர்களுக்காக காத்திருப்பார்கள், ஏனெனில் அத்தகைய குறியீட்டில் பிழைகளைக் கண்டறிவது மிகவும் கடினமான பணியாகும். இந்த தீர்வின் மற்றொரு சிக்கல் என்னவென்றால், சில தத்துவவாதிகள் பசியுடன் இருக்க மாட்டார்கள் என்று இது உத்தரவாதம் அளிக்காது.

கலப்பின தீர்வுகள்

நாம் பயனர் பயன்முறை மற்றும் சுழற்சியில் இருக்கும் போது, ​​மற்றும் கர்னல் மூலம் நூலைத் தடுக்கும் போது, ​​நேரத்திற்கான இரண்டு அணுகுமுறைகளைப் பார்த்தோம். முதல் முறை குறுகிய பூட்டுகளுக்கு நல்லது, இரண்டாவது நீண்டது. ஒரு சுழற்சியில் ஒரு மாறி மாறுவதற்கு முதலில் சுருக்கமாக காத்திருக்க வேண்டியது அவசியம், பின்னர் காத்திருப்பு நீண்டதாக இருக்கும்போது நூலைத் தடுக்கவும். இந்த அணுகுமுறை என்று அழைக்கப்படும் செயல்படுத்தப்படுகிறது. கலப்பின கட்டமைப்புகள். கர்னல் பயன்முறையில் உள்ள அதே கட்டுமானங்கள் இங்கே உள்ளன, ஆனால் இப்போது பயனர் பயன்முறை வளையத்துடன்: SemaphorSlim, ManualResetEventSlim முதலியன இங்கே மிகவும் பிரபலமான வடிவமைப்பு Monitor, ஏனெனில் C# இல் நன்கு அறியப்பட்ட ஒன்று உள்ளது lock தொடரியல். Monitor இது 1 (mutex) இன் அதிகபட்ச மதிப்பைக் கொண்ட அதே செமாஃபோர் ஆகும், ஆனால் லூப்பில் காத்திருப்பு, மறுநிகழ்வு, நிபந்தனை மாறி முறை (கீழே உள்ள மேலும்) போன்றவற்றிற்கான ஆதரவுடன். அதனுடன் ஒரு தீர்வைப் பார்ப்போம்.

// Спрячем объект для Монитора от всех, чтобы без дедлоков.
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);
    }
}

முட்கரண்டிகளுக்கான அணுகலுக்காக இங்கே நாங்கள் முழு அட்டவணையையும் மீண்டும் தடுக்கிறோம், ஆனால் இப்போது நாங்கள் எல்லா நூல்களையும் ஒரே நேரத்தில் தடுக்கிறோம், யாராவது சாப்பிட்டு முடித்தவுடன் அண்டை வீட்டாரை அல்ல. அந்த. முதலில், யாரோ ஒருவர் சாப்பிட்டு, அண்டை வீட்டாரைத் தடுக்கிறார், இதை ஒருவர் முடித்தவுடன், ஆனால் உடனடியாக மீண்டும் சாப்பிட விரும்புகிறார், அவர் தடுப்பிற்குச் சென்று தனது அண்டை வீட்டாரை எழுப்புகிறார், ஏனெனில். அதன் காத்திருப்பு நேரம் குறைவாக உள்ளது.

இப்படித்தான் முட்டுக்கட்டைகள் மற்றும் சில தத்துவஞானிகளின் பட்டினியைத் தவிர்க்கிறோம். நாங்கள் ஒரு குறுகிய காத்திருப்புக்கு ஒரு வளையத்தைப் பயன்படுத்துகிறோம் மற்றும் நீண்ட காலத்திற்கு நூலைத் தடுக்கிறோம். தீர்வில் உள்ளதைப் போல, அனைவரையும் ஒரே நேரத்தில் தடைநீக்குவது, பக்கத்து வீட்டுக்காரர் மட்டும் தடைநீக்கப்பட்டதை விட மெதுவாக இருக்கும் AutoResetEvent, ஆனால் வேறுபாடு பெரியதாக இருக்கக்கூடாது, ஏனென்றால் நூல்கள் முதலில் பயனர் பயன்முறையில் இருக்க வேண்டும்.

У lock தொடரியல் மோசமான ஆச்சரியங்களைக் கொண்டுள்ளது. பயன்படுத்த பரிந்துரைக்கிறோம் Monitor நேரடியாக [ரிக்டர்] [எரிக் லிப்பர்ட்]. அதில் ஒன்று அது lock எப்போதும் வெளியே Monitor, ஒரு விதிவிலக்கு இருந்தாலும், மற்றொரு நூல் பகிரப்பட்ட நினைவக நிலையை மாற்றலாம். இதுபோன்ற சந்தர்ப்பங்களில், முட்டுக்கட்டைக்குச் செல்வது அல்லது எப்படியாவது பாதுகாப்பாக நிரலை நிறுத்துவது நல்லது. மற்றொரு ஆச்சரியம் என்னவென்றால், மானிட்டர் ஒத்திசைவுத் தொகுதிகளைப் பயன்படுத்துகிறது (SyncBlock), இது அனைத்து பொருட்களிலும் உள்ளது. எனவே, ஒரு பொருத்தமற்ற பொருள் தேர்ந்தெடுக்கப்பட்டால், நீங்கள் எளிதாக ஒரு முட்டுக்கட்டைப் பெறலாம் (உதாரணமாக, நீங்கள் உள்ளிணைந்த சரத்தில் பூட்டினால்). இதற்கு எப்போதும் மறைந்திருக்கும் பொருளைப் பயன்படுத்துகிறோம்.

நிபந்தனை மாறி முறை சில சிக்கலான நிலையின் எதிர்பார்ப்பை இன்னும் சுருக்கமாக செயல்படுத்த அனுமதிக்கிறது. .NET இல், அது முழுமையடையாதது, என் கருத்துப்படி, ஏனெனில் கோட்பாட்டில், பல மாறிகளில் பல வரிசைகள் இருக்க வேண்டும் (Posix Threads போல), மற்றும் ஒரு லோக்கில் அல்ல. பின்னர் அனைத்து தத்துவஞானிகளுக்கும் அவற்றை உருவாக்க முடியும். ஆனால் இந்த வடிவத்தில் கூட, இது குறியீட்டைக் குறைக்க உங்களை அனுமதிக்கிறது.

பல தத்துவவாதிகள் அல்லது 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 தத்துவவாதிகளின் வேலை. மானிட்டருடன் முந்தைய தீர்வு முதல் 4 திரிகளை மட்டுமே இயக்கியது, மீதமுள்ளவை இயங்கவில்லை. இந்த 4 இழைகள் ஒவ்வொன்றும் சுமார் 2ms வரை செயலற்ற நிலையில் இருந்தன. மேலும் ஒத்திசைவு / காத்திருப்பு தீர்வு 100 இல் இயங்கியது, ஒவ்வொன்றும் சராசரியாக 6.8 வினாடிகள் காத்திருக்கும். நிச்சயமாக, உண்மையான அமைப்புகளில், 6 வினாடிகள் செயலற்ற நிலையில் இருப்பது ஏற்றுக்கொள்ள முடியாதது மற்றும் இது போன்ற பல கோரிக்கைகளை செயல்படுத்தாமல் இருப்பது நல்லது. மானிட்டருடனான தீர்வு அளவிட முடியாததாக மாறியது.

முடிவுக்கு

இந்த சிறிய எடுத்துக்காட்டுகளிலிருந்து நீங்கள் பார்க்க முடியும், .NET பல ஒத்திசைவு கட்டுமானங்களை ஆதரிக்கிறது. இருப்பினும், அவற்றை எவ்வாறு பயன்படுத்துவது என்பது எப்போதும் தெளிவாகத் தெரியவில்லை. இந்த கட்டுரை பயனுள்ளதாக இருந்தது என்று நம்புகிறேன். இப்போதைக்கு, இது முடிவு, ஆனால் இன்னும் நிறைய சுவாரஸ்யமான விஷயங்கள் உள்ளன, எடுத்துக்காட்டாக, நூல்-பாதுகாப்பான சேகரிப்புகள், TPL டேட்டாஃப்ளோ, ரியாக்டிவ் புரோகிராமிங், மென்பொருள் பரிவர்த்தனை மாதிரி போன்றவை.

ஆதாரங்கள்

ஆதாரம்: www.habr.com

கருத்தைச் சேர்