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، مګر د هر ډول اسانتیاو سره: د نورو دندو بلاک وروسته د دندې پرمخ وړلو وړتیا ، له دندو څخه یې بیرته راګرځول ، په اسانۍ سره یې مداخله کول او نور ډیر څه. etc. دوی د async / انتظار جوړونې مالتړ لپاره اړین دي (د کار پراساس اسینکرونوس نمونه ، د IO عملیاتو لپاره انتظار کولو لپاره ترکیب شوګر). په دې اړه به وروسته خبرې وکړو.
که څه هم دا اړینه نده چې په ځانګړي ډول ډیر خواړه فکر وکړئ ، مګر لوږه هرڅوک د فلسفې پریښودو لامل کیږي. راځئ هڅه وکړو چې زموږ په ستونزه کې د تارونو لوږې وضعیت سم کړو. لوږه هغه وخت ده چې تار چلوي، مګر د پام وړ کار پرته، په بل عبارت، دا هماغه بند دی، اوس یوازې تاری نه ویده کیږي، په فعاله توګه د خوړلو لپاره د څه په لټه کې وي، مګر خواړه شتون نلري. د پرله پسې بندیدو مخنیوي لپاره ، موږ به فورک بیرته واچوو که چیرې موږ نشو کولی یو بل واخلو.
// То же что и в 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 ځله لږ خوري. نو په کوډ کې یوه کوچنۍ تېروتنه د فعالیت کمیدو لامل کیږي. دلته دا هم د یادولو وړ ده چې یو نادر حالت هغه وخت ممکن دی چې ټول فیلسوفان کیڼ اړخ واخلي، ښی اړخ نه وي، دوی کیڼ اړخ کېږدي، انتظار وکړئ، بیا کیڼ اړخ واخلي، او داسې نور. دا حالت هم د لوږې په څیر دی، نور د بند په څیر. زه یې په تکرارولو کې پاتې راغلم. لاندې د داسې حالت لپاره یو انځور دی چې دوه بد فیلسوفانو دواړه فورکونه اخیستي او دوه ښه یې لوږه دي.
ښه، بله ستونزه چې کولی شي د فیلسوفانو په شانداره ډوډۍ کې مداخله وکړي که چیرې د دوی څخه یو ناڅاپه د هغه په لاسونو کې د ټوټو سره مړ شي (او دوی به هغه ورته ښخ کړي). بیا به ګاونډیان له ډوډۍ پرته پاتې کیدل. تاسو کولی شئ پخپله د دې قضیې لپاره د مثال کوډ سره راشي، د بیلګې په توګه، دا غورځول شوی 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 (چې هلته کارول کیږي). اوس هغه پوهیږي چې څنګه د انتظار خلکو شمیره وکړي، لږ خوب ته یې واچوي، او نور ډیر څه. او داسې نور. مګر موږ باید په یاد ولرو چې دا لاهم هماغه فعال جریان دی چې د پروسیسر سرچینې خوري او جریان ساتي ، کوم چې د لوږې لامل کیدی شي که چیرې یو فیلسوف د نورو په پرتله ډیر لومړیتوب ولري ، مګر د سرو زرو فورک نلري (د لومړیتوب انډول ستونزه) . له همدې امله، موږ دا یوازې په شریکه حافظه کې د خورا لنډ بدلونونو لپاره کاروو، پرته له دې چې د دریمې ډلې زنګونو، نیست شوي لاکونو، او نورو حیرانتیاو څخه.
// Для блокирования отдельного философа.
// Инициализируется: 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();
}
// Запуск такой же, как раньше. Где-нибудь в программе:
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 لپاره بې کاره و. او د async / انتظار حل ټول 100 پرمخ وړل شوي، په اوسط ډول هر یو د 6.8 ثانیو انتظار سره. البته ، په ریښتیني سیسټمونو کې ، د 6 ثانیو لپاره بې کاره د منلو وړ ندي او دا غوره ده چې د دې په څیر ډیری غوښتنې پروسس نه کړئ. د مانیټر سره حل په هیڅ ډول د توزیع وړ نه و.
پایلې
لکه څنګه چې تاسو د دې کوچنیو مثالونو څخه لیدلی شئ، .NET د ډیری ترکیب جوړونې ملاتړ کوي. په هرصورت، دا تل روښانه نه ده چې څنګه یې کارول کیږي. زه امید لرم چې دا مقاله ګټوره وه. د اوس لپاره ، دا پای دی ، مګر لاهم ډیر په زړه پوري شیان پاتې دي ، د مثال په توګه ، د تار خوندي راټولول ، د TPL ډیټا فلو ، د عکس العمل برنامه کول ، د سافټویر لیږد ماډل ، او داسې نور.