ವೆಲ್-ಫೆಡ್ ಫಿಲಾಸಫರ್ಸ್ ಅಥವಾ ಸ್ಪರ್ಧಾತ್ಮಕ .NET ಪ್ರೋಗ್ರಾಮಿಂಗ್

ವೆಲ್-ಫೆಡ್ ಫಿಲಾಸಫರ್ಸ್ ಅಥವಾ ಸ್ಪರ್ಧಾತ್ಮಕ .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), ಇದು ಪರಮಾಣು ಅನುಕ್ರಮ ಓದುವಿಕೆ ಮತ್ತು ಬರವಣಿಗೆಗಾಗಿ ಮೆಮೊರಿಯ ತುಣುಕನ್ನು ಲಾಕ್ ಮಾಡುತ್ತದೆ. ಮತ್ತು ಸ್ಪಿನ್‌ವೇಟ್ ನಿರ್ಮಾಣಕ್ಕೆ ಸಮನಾಗಿರುತ್ತದೆ 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 (ಮ್ಯೂಟೆಕ್ಸ್) ಗರಿಷ್ಠ ಮೌಲ್ಯವನ್ನು ಹೊಂದಿರುವ ಅದೇ ಸೆಮಾಫೋರ್ ಆಗಿದೆ, ಆದರೆ ಲೂಪ್, ರಿಕರ್ಶನ್, ಕಂಡಿಶನ್ ವೇರಿಯಬಲ್ ಪ್ಯಾಟರ್ನ್ (ಕೆಳಗಿನವುಗಳಲ್ಲಿ ಹೆಚ್ಚಿನವು) ನಲ್ಲಿ ಕಾಯುವ ಬೆಂಬಲದೊಂದಿಗೆ. ಇದರೊಂದಿಗೆ ಪರಿಹಾರವನ್ನು ನೋಡೋಣ.

// Спрячем объект для Монитора от всех, чтобы без дедлоков.
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 ನಲ್ಲಿ ಇದು ಅಪೂರ್ಣವಾಗಿದೆ, ನನ್ನ ಅಭಿಪ್ರಾಯದಲ್ಲಿ, ಏಕೆಂದರೆ... ಸಿದ್ಧಾಂತದಲ್ಲಿ, ಹಲವಾರು ವೇರಿಯೇಬಲ್‌ಗಳಲ್ಲಿ (ಪಾಸಿಕ್ಸ್ ಥ್ರೆಡ್‌ಗಳಂತೆ) ಹಲವಾರು ಕ್ಯೂಗಳು ಇರಬೇಕು ಮತ್ತು ಒಂದು ಲಾಕ್‌ನಲ್ಲಿ ಅಲ್ಲ. ಆಗ ಅವುಗಳನ್ನು ಎಲ್ಲಾ ತತ್ವಜ್ಞಾನಿಗಳಿಗೆ ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ. ಆದರೆ ಈ ರೂಪದಲ್ಲಿ ಸಹ ಕೋಡ್ ಅನ್ನು ಕಡಿಮೆ ಮಾಡಲು ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ.

ಅನೇಕ ತತ್ವಜ್ಞಾನಿಗಳು ಅಥವಾ 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

ಕಾಮೆಂಟ್ ಅನ್ನು ಸೇರಿಸಿ