Критична секція

Критична секція (англ. critical section) — об'єкт синхронізації потоків у Windows, що дозволяє запобігти одночасному виконанню деякого критичного набору операцій (зазвичай пов'язаних з доступом до даних) кількома нитями.

Загально

Об'єкт критичної секції представляє ділянку сирцевого коду, яка в кожен окремий момент може виконуватися лише одною ниттю. Критичні секції можна створювати і знищувати, але вони не мають дескрипторів, тому не можуть використовуватись різними процесами. У той же час, для синхронізації нитей одного процесу критичні секції – один з найшвидших і найпростіших способів. Об'єкти критичних секцій не є об'єктами ядра, а розміщуються у користувацькому адресному просторі процесу[1].

В більшості реалізацій з кожною критичною секцією пов'язано лічильник, і захоплення критичної секції можливе лише за нульового значення цього лічильника. Захоплення означає атомарну зміну лічильника на 1. Щоразу, коли нить, що володіє критичною секцією, знов входить до неї, лічильник збільшується на 1, при виході — зменшується. Перехід до чекання на події ядра здійснюється лише у випадку, якщо значення змінної лічильника до спроби захоплення було вже більше 1, тобто критична секція зайнята і є реальне «змагання» двох або більше нитей за ресурс критичної секції.

Аналогічний об'єкт в Linux називається ф'ютекс.

Порівняння критичних секцій та м'ютексів

Критична секція значною мірою виконує ті ж завдання, що і м'ютекс.

У операційних системах Microsoft Windows головна відмінність між м'ютексом і критичною секцією в тому, що м'ютекс є об'єктом ядра і може бути використаний кількома процесами одночасно, критична секція ж належить процесу і служить для синхронізації тільки його нитей. Процедура входу і виходу критичних секцій зазвичай займає менший час, ніж аналогічні операції м'ютекса.

Між м'ютексом і критичною секцією є й термінологічні відмінності. Процедура, аналогічна захопленню м'ютекса, називається входом у критичну секцію, зняття блокування м'ютекса — виходом з критичної секції.

Робота з критичними секціями у Windows

Для роботи з критичними секціями використовують Windows API.

Перед використання критичної секції її слід ініціалізувати за допомогою процедури InitializeCriticalSection. Її опис мовою C[2]:

void WINAPI InitializeCriticalSection(
 __out  LPCRITICAL_SECTION lpCriticalSection
);

Опис мовою Delphi:

procedure InitializeCriticalSection(
  var lpCriticalSection: TRTLCriticalSection
);

Перед входом у код критичної секції нить має викликати функцію EnterCriticalSection. Її опис мовою C[3]:

void WINAPI EnterCriticalSection(
 __inout  LPCRITICAL_SECTION lpCriticalSection
);

Якщо в цей момент жодна з нитей процесу не володіє критичною секцією, то вона стає власником її об'єкта і продовжує виконання. Якщо секція вже захоплена іншою ниттю, викликаюча нить зупиняється до її звільнення. Нить, яка володіє критичною секцією, може повторно викликати EnterCriticalSection без блокування свого виконання.

Відразу після закінчення роботи зі спільним ресурсом нить повинна звільнити об'єкт критичної секції викликом процедури LeaveCriticalSection. Її опис мовою C[4]:

void WINAPI LeaveCriticalSection(
 __inout  LPCRITICAL_SECTION lpCriticalSection
);

Опис мовою Delphi:

procedure LeaveCriticalSection(
  var lpCriticalSection: TRTLCriticalSection
);

Вона звільняє об’єкт критичної секції, і якщо в цей час існують інші ниті, які очікують її звільнення, одна з них стає новим власником і продовжує виконання. Якщо нить завершиться, не звільнивши критичної секції, вона переходить у невизначений стан і робота програми може бути заблокована.

Для кожної критичної секції система підтримує лічильник входжень, тому нить повинна звільнити її стільки разів, скільки захоплювала. Недотримання цієї умови приведе до блокування ниті ("зависання").

Слід також пам'ятати, що спроба виходу з критичної секції, якою нить не володіє, також приведе до блокування викликаючої ниті.

Програма може здійснити спробу захоплення об'єкта критичної секції без зупинки ниті. Для цього використовують функцію TryEnterCriticalSection (опис мовою C[5]):

BOOL WINAPI TryEnterCriticalSection(
 __inout  LPCRITICAL_SECTION lpCriticalSection
);

Опис функції на Delphi:

function TryEnterCriticalSection(
  var lpCriticalSection: TRTLCriticalSection
): BOOL;

У момент виклику вона перевіряє, чи критична секція захоплена якоюсь іншою ниттю. Якщо так — функція повертає false, у протилежному випадку вона захоплює критичну секцію та повертає true.

Після завершення роботи з критичною секцією вона повинна бути вивільнена викликом процедури DeleteCriticalSection. Опис на C[6]:

void WINAPI DeleteCriticalSection(
 __inout  LPCRITICAL_SECTION lpCriticalSection
);

Опис на Delphi:

procedure DeleteCriticalSection(
  var lpCriticalSection: TRTLCriticalSection
);

Приклад використання критичної секції

Нижче приведено фрагмент коду (мовою Delphi[1]), який забезпечує синхронізацію роботи ниті при звертанні до спільної (для кількох нитей) змінної A. Задля усунення неоднозначності, викликаної конкуренцією потоків, подібний фрагмент програми повинен бути у всіх нитях, які працюють зі змінною A. Якщо подібної синхронізації не використовувати, то виникатиме конкуренція потоків.

// Оголошення критичної секції
var ICriSec : TRTLCriticalSection;
...................................
// Ініціалізація - до запуску першої ниті
InitializeCriticalSection(ICriSec);
...................................
// Функція ниті
function ThreadFunc(Ptr: Pointer): LongInt;
var I : integer;
begin
 for I := 1 to 1000000 do begin
   // Якийсь код
   EnterCriticalSection(ICriSec);
   Inc(A);
   LeaveCriticalSection(ICriSec);
   // Якийсь код
 end;
end;
....................................
// Знищення об'єкта критичної секції
DeleteCriticalSection(ICriSec);

Примітки

Див. також


This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.