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, ин барои ҳолатҳое муфид аст, ки мо бояд афзалияти риштаро тағир диҳем, вақте ки мо амалиёти тӯлонӣ дорем, барои риштаи Foreground ва ғайра.
Ба ибораи дигар, System.Threading.Tasks.Task синф ҳамон аст Thread, аммо бо ҳама гуна қулайҳо: қобилияти оғоз кардани вазифа пас аз блоки дигар вазифаҳо, баргардонидани онҳо аз функсияҳо, ба таври қулай қатъ кардани онҳо ва ғайра. ва ғайра. Онҳо барои дастгирии конструксияҳои асинхронӣ/интизорӣ лозиманд (Шакли асинхронии ба вазифа асосёфта, шакари синтаксисӣ барои интизории амалиёти IO). Мо дар ин бора баъдтар гап мезанем.
CancelationTokenSource дар ин ҷо зарур аст, ки ришта метавонад бо як сигнал аз риштаи даъваткунанда худашро қатъ кунад.
Ҳарчанд барои фикр кардан ба шумо ғизои зиёд лозим нест, гуруснагӣ метавонад ҳар касро маҷбур кунад, ки аз фалсафа даст кашад. Биёед кӯшиш кунем, ки вазъияти гуруснагии риштаро дар мушкилоти худ тақлид кунем. Гуруснагӣ вақте аст, ки ришта кор мекунад, аммо бидуни кори назаррас, ба ибораи дигар, ин ҳамон бунбаст аст, танҳо ҳоло ришта хоб намекунад, аммо фаъолона чизе меҷӯяд, аммо ғизо нест. Барои он ки зуд-зуд банд нашавед, агар дигарашро гирифта натавонем, чангакро баргардонем.
// То же что и в 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 маротиба камтар мехӯранд. Ҳамин тавр, хатои хурд дар код боиси паст шудани кор мегардад. Дар ин ҷо ҳам бояд қайд кард, ки як ҳолати нодир имконпазир аст, ки ҳама файласуфон чанголи чапро мегиранд, рост нест, чапро мегузоранд, интизор мешаванд, чапро боз мегиранд ва ғайра. Ин ҳолат низ гуруснагӣ аст, бештар ба бастани мутақобила монанд аст. Ман онро такрор карда натавонистам. Дар зер тасвире барои вазъияте оварда шудааст, ки ду файласуфи бад ҳарду чангакро гирифтаанд ва ду файласуфи хуб гуруснагӣ мекашанд.
Роҳи соддатарин барои файласуфон ин аст, ки ҳамеша аз пешхизмат барои дастрасӣ ба вилкаҳо дархост кунанд. Онхое. Акнун файласуфон дар наздикии он вилка интизор намешаванд, балки мунтазир мешаванд ё аз пешхизмат мепурсанд. Дар аввал мо барои ин танҳо Фазои корбарро истифода мебарем; дар он мо барои даъват кардани ягон расмиёт аз ядро (маълумот дар бораи онҳо дар поён) аз танаффус истифода намебарем.
Ҳалли фазои корбар
Дар ин ҷо мо ҳамон кореро, ки қаблан бо як чангол ва ду файласуф карда будем, мекунем, дар як ҳалқа меронем ва интизор мешавем. Аммо ҳоло он ҳама файласуфон хоҳад буд ва ба тавре ки гӯё танҳо як чангак, яъне. гуфтан мумкин аст, ки фацат файласуф, ки аз пешхизмат ин «чанги тиллой»-ро гирифтааст, мехурад. Барои ин мо 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 (ки дар он ҷо истифода мешавад). Акнун вай медонад, ки чӣ тавр интизориҳоро ҳисоб кунад, онҳоро каме хоб кунад ва ғайра. ва ғайра Дар маҷмӯъ, он ҳама кори имконпазир барои оптимизатсия мекунад. Аммо мо бояд дар хотир дошта бошем, ки ин ҳамон ҳалқаи фаъолест, ки захираҳои протсессориро мехӯрад ва риштаро нигоҳ медорад, ки метавонад ба гуруснагӣ оварда расонад, агар яке аз файласуфон нисбат ба дигарон авлавият пайдо кунад, аммо чароғаки тиллоӣ надошта бошад (проблемаи Priority Inversion). ). Аз ин рӯ, мо онро танҳо барои тағироти хеле кӯтоҳ дар хотираи муштарак бидуни зангҳои тарафи сеюм, қулфҳои лона ва дигар сюрпризҳо истифода мебарем.
Тарҳи асосии ин ҷо семафорест, ки аз ҷониби Дижкстра зиёда аз ним аср пеш пешниҳод карда шудааст. Семафор, содда карда гӯем, як адади мусбатест, ки аз ҷониби система идора карда мешавад ва ду амал дар он - афзоиш ва камшавӣ. Агар сифрро кам кардан имконнопазир бошад, пас риштаи занг баста мешавад. Вақте ки шумора бо ягон риштаи дигар/раванди фаъол зиёд мешавад, он гоҳ риштаҳо гузаронида мешаванд ва семафор боз ба миқдори гузашта кам мешавад. Шумо метавонед қаторҳоро дар тангно бо семафор тасаввур кунед. .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, вале тафовут набояд калон бошад, зеро риштаҳо бояд аввал дар ҳолати корбар боқӣ монанд.
Намунаи тағирёбандаи шарт ба шумо имкон медиҳад, ки интизории баъзе ҳолати мураккабро дақиқтар иҷро кунед. Дар .NET он нопурра аст, ба назари ман, зеро... Дар назария, дар якчанд тағирёбанда (ба мисли Posix Threads) бояд якчанд навбат бошад, на дар як қулф. Он гох онхоро барои хамаи файласуфон сохтан мумкин мебуд. Аммо ҳатто дар ин шакл он ба шумо имкон медиҳад, ки кодро кӯтоҳ кунед.
Бисёр файласуфон ё async / await
Хуб, ҳоло мо метавонем риштаҳоро ба таври муассир маҳкам кунем. Аммо агар мо файласуфони зиёд дошта бошем-чй? 100? 10000? Масалан, мо ба сервери веб 100000 4 дархост гирифтем. Эҷоди ришта барои ҳар як дархост гарон хоҳад буд, зеро ин кадар риштахо дар баробари ичро карда намешаванд. Танҳо ҳамон қадар ядроҳои мантиқӣ иҷро карда мешаванд (ман XNUMX дорам). Ва ҳар каси дигар танҳо захираҳоро аз даст медиҳанд. Як роҳи ҳалли ин мушкилот намунаи асинх/интизор аст. Идеяи он аз он иборат аст, ки функсия риштаро нигоҳ намедорад, агар он барои идомаи чизе интизор шавад. Ва ҳангоме ки чизе рӯй медиҳад, он иҷрои худро идома медиҳад (вале на ҳатман дар ҳамон ришта!). Дар ҳолати мо, мо як чангакро интизор мешавем.
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 ришта тақрибан 2ms бекор буд. Ва ҳалли асинхронӣ / интизорӣ ҳама 100-ро иҷро кард, ки ҳар як интизорӣ ба ҳисоби миёна 6.8 сонияро ташкил медиҳад. Албатта, дар системаҳои воқеӣ 6 сония бекор мондан ғайри қобили қабул аст ва беҳтар аст, ки ин қадар дархостҳоро ин тавр коркард накунед. Ҳалли бо Monitor тамоман миқёспазир набуд.
хулоса
Тавре ки шумо аз ин мисолҳои хурд мебинед, .NET бисёр сохторҳои ҳамоҳангсозиро дастгирӣ мекунад. Бо вуҷуди ин, на ҳама вақт маълум аст, ки чӣ тавр истифода бурдани онҳо. Ман умедворам, ки ин мақола муфид буд. Мо ҳоло инро ҷамъбаст карда истодаем, аммо то ҳол чизҳои ҷолиби зиёде боқӣ мондаанд, масалан, коллексияҳои аз ришта бехатар, Dataflow TPL, барномасозии реактивӣ, модели транзаксияҳои нармафзор ва ғайра.