Взаємоблокування – загальний|спільний| синдром багатьох об'єктів синхронізації. Хороше|добре| правило, що допомагає уникати взаємоблокуваннь |, полягає в тому, аби|щоб| починати з блокування мінімальної кількості об'єктів, і збільшувати міру|ступінь| деталізації блокувань, коли розмір коди в блокуванні надмірно збільшується.
Заблокований потік може бути передчасно розблокований двома шляхами:
Це повинно бути зроблено з|із| іншого потоку; чекаючий потік безсилий що-небудь зробити в блокованому стані|статку|.
Interrupt|
Виклик Interruptдля блокованого потоку примусово звільняє його з генерацією виключення ThreadInterruptedException, як показано в наступному прикладі:
class| Program|
{
static| void| Main|()
{
Thread| t = new| Thread|(delegate|()
{
try|
{
Thread|.Sleep(Timeout|.Infinite);
}
catch|(ThreadInterruptedException|)
{
Console|.Write("Forcibly| ");
}
Console|.WriteLine("Woken|!");
});
t.Start();
t.Interrupt();
}
}
Wait| Handles|
Оператор lock – один з прикладів конструкцій синхронізації потоків. Lockє самим відповідним засобом для організації монопольного доступу до ресурсу або секції коди, але є завдання синхронізації (типа подачі сигналу початку роботи чекаючому потоку), для яких lockбуде не найадекватнішим і зручнішим засобом.
У Win32 API є багатий набір конструкцій синхронізації, і вони доступні в .NET Framework у вигляді класів EventWaitHandle, Mutexі Semaphore. Деякі з них практичний за інших: Mutex, наприклад, здебільше дублює можливості lock, тоді як EventWaitHandleнадає унікальні можливості сигналізації.
Всі три класи засновано на абстрактному класі WaitHandle, але вельми відрізняються по поведінці. Одна із загальних особливостей – це здатність іменування, що робить можливою роботу з потоками не лише одного, але і різних процесів.
EventWaitHandleмає два похідних класу – AutoResetEventі ManualResetEvent (що не мають жодного відношення до подій і делегатів C#). Обом класам доступні всі функціональні можливості базового класу, єдина відмінність полягає у виклику конструктора базового класу з різними параметрами.
У частині продуктивності, все WaitHandleзазвичай виконуються в районі декількох мікросекунд. Це рідко має значення з врахуванням контексту, в якому вони застосовуються.
AutoResetEventдуже схожий на турнікет – один квиток дозволяє пройти одній людині. Приставка “auto” в назві відноситься до того факту, що відкритий турнікет автоматично закривається або “скидається” після того, як дозволяє кому-небудь пройти. Потік блокується в турнікета викликом WaitOne (чекати (wait) в даного (one) турнікета, поки він не відкриється), а квиток вставляється викликом методу Set. Якщо декілька потоків викликають WaitOne, за турнікетом утворюється черга. Квиток може “вставити” будь-який потік – іншими словами, будь-який (неблокований) потік, що має доступ до об'єкту AutoResetEvent, може викликати Set, аби пропустити один блокований потік.
Якщо Setвикликається, коли немає чекаючих потоків, хэндл знаходитиметься у відкритому стані, поки який-небудь потік не викличе WaitOne. Ця особливість допомагає уникнути гонок між потоком, відповідним до турнікета, і потоком, що вставляє квиток (“опа, квиток вставлений на мікросекунду раніше, дуже шкода, але вам доведеться почекати ще скільки-небудь!”). Проте багатократний виклик Setдля вільного турнікета не дозволяє пропустити за раз цілий натовп – зможе пройти лише один людина, всі останні квитки будуть витрачені даремно.
WaitOneприймає необов'язковий параметр timeout– метод повертає false, якщо чекання закінчується по таймауту, а не по здобуттю сигналу. WaitOneтакож можна виучити виходити з поточного контексту синхронізації для продовження чекання (якщо використовується режим з автоматичним блокуванням) щоб уникнути надмірного блокування.
Метод Resetзабезпечує закриття відкритого турнікета, без всяких чекань і блокувань.
Мьютекс забезпечує ті ж самі функціональні можливості, що і оператор lockв C#, що робить його таким, що не дуже зажадався. Єдина перевага полягає в тому, що Mutexдоступний з різних процесів, забезпечуючи блокування на рівні комп'ютера, у відмінності від оператора lock, який діє лише на рівні додатка. Mutexвідносно швидкий, але lockшвидше в сотні разів. Здобуття мьютекса займає декілька мікросекунд, виклик lock– десятки Наносекунди (якщо не відбувається власне блокування). Метод WaitOneдля Mutexотримує виняткове блокування, блокуючи потік, якщо це необхідно. Виняткове блокування може бути зняте викликом методу ReleaseMutex. Точно також як оператор lockв C#, Mutexможе бути звільнений лише з того ж потоку, що його захопив.
Типове використання мьютекса| для взаємодії процесів – забезпечення можливості|спроможності| запуску лише|тільки| одного екземпляра|примірника| програми одноразово. Приклад:
class| OneAtATimePlease|
{
static| Mutex| mutex| = new| Mutex|(false|, "oreilly|.com OneAtATimeDemo|");
static| void| Main|()
{
if| (!mutex.WaitOne(TimeSpan|.FromSeconds(5), false|))
{
Console|.WriteLine("У системі запущений інший екземпляр|примірник| програми!");
return|;
}
try|
{
Console|.WriteLine("Працюємо - натискуйте|натискайте| Enter| для виходу...");
Console|.ReadLine();
}
finally| { mutex|.ReleaseMutex(); }
}
}
Корисна властивість Mutex – якщо додаток завершується без виклику ReleaseMutex, CLR звільняє мьютекс автоматично.
Semaphoreз ємкістю, рівній одиниці, подібний Mutexабо lock, за винятком того, що він не має потоку-господаря. Будь-який потік може викликати Releaseдля Semaphore, тоді як у випадку з Mutexабо lockлише потік, що захопив ресурс, може його звільнити.
У наступному прикладі по черзі запускаються десять потоків, що виконують виклик Sleep. Semaphoreгарантує, що не більше трьох потоків можуть викликати Sleepодночасно:
class| SemaphoreTest|
{
static| Semaphore| s = new| Semaphore|(3, 3); // Available=3|; Capacity=3|
static| void| Main|()
{
for| (int| i = 0; i < 10; i++|)
new| Thread|(Go|).Start();
}
static| void| Go|()
{
while| (true|)
{
s.WaitOne();
// Лише|тільки| 3 потоки можуть знаходитися|перебувати| тут одночасно
Thread|.Sleep(100);
s.Release();
}
}
}
Завдання синхронізації двох процесів полягає в тому, що в одному процесі, наприклад Б, у визначеній точці (точці події) виконується подія (обчислення даних, уведення або виведення даних і т. ін.), а другий процес А у визначеній точці (точці очікування події) блокується доти, доки ця подія не відбудеться і він зможе продовжити своє виконання. Точка події і точка очікування - це точки синхронізації Якщо процес А вийшов на точку синхронізації, коли подія вже відбулася, то він не блокується і продовжує виконуватись.
Існує декілька схем синхронізації процесів :
• один процес очікує на подію в одному процесі (а);
• декілька процесів очікують на подію в одному процесі (b);
• один процес очікує на події в кількох процесах (с).
Для вирішення завдання синхронізації, яке іноді називають синхронізацією за подіями (eventsynchronization), можна використовувати різні механізми синхронізації процесів, таки як семафори, події, монітори.
Для вирішення завдання синхронізації в мові Ада95 можна застосувати механізм семафорів, а також захищені модулі.
Застосування семафорів. Розглянемо приклад, у якому задача А чекає на подію, яку мають містити задачі В, наприклад, введення даних (змінної х). Для синхронізації процесів з уведення використовуємо семафор СігналПроПодію з початковим значенням false, яке встановлюється автоматично під час створювання семафора. Очікування події в процесі А реалізовано за допомогою операції Suspend__Until_True (СігналПроПодію), а посилання сиггалу про подію в задачі В реалізовано за допомогою операції SetTrue(СігналПроПодію) .
procedureСинхронізація is
X: integer; -- глобальна змінна
СігналПроПодію: Suspension_Object; -- семафор
— задача, що чекає на подію
taskA;
task body A is
begin
точка очікування події
Suspend_Until_True(СігналПроПодію);
end A;
-- задача, де пройде подія
task В;
task body В is
begin
get(X); -- введення даних(подія, наяку чекає А)
Set_True(СігналПроПодію); -- сигнал задачі А
end В; begin
null ;
end Синхронізація;
Механізм семафорів запропонував математик Е.Дейкстра. У класичній інтерпретації механізм семафорів - це спеціальний захищений тип Semaphore та дві неподільні операції над змінною цього типу: P(S) і V(S).Неподільність операції означає, що її не можна переривати, поки не завершиться її виконання.
У мови Ада механізм семафорів подано у вигляді пакета Synchronous_Task_Control в додатку Annex D: Real-Time Systems. Пакет реалізує механізм семафорів таким чином. Семафорний тип забезпечується приватним типом Suspension_object, операції P(S) і V(S) реалізовані за допомогою процедур Suspend_Until_True () і SetJTrue (). Використовується бінарний логічний семафор, тобто семафорні змінні типу Suspension_Object набувають значень false і true. Крім указаних процедур, в пакеті реалізовані допоміжні процедури SetFalseOдля встановлення значення семафора в false і Currentstate () для зчитування поточного значення семафора. Специфікація пакета:
package Ada.Synchronous_Task_Control is
type Suspension_Object limited private;
rocedure Set_True(S : in out 3uspension_Object);
procedure Set_False(S : in out Suspension_Object);
function Current_State(S : Suspension_Object) return Boolean;
procedure Suspend_Until_True(3:in out Suspension_Object); private