Чим відрізняється мютекс від критичного розділу?


134

Поясніть, будь ласка, з точки зору Linux, Windows?

Я програмую на C #, чи змінили б ці два терміни. Будь ласка, опублікуйте скільки завгодно, із прикладами та подібними…

Дякую

Відповіді:


232

Для Windows критичні розділи мають меншу вагу, ніж мютекси.

Mutexes може бути розподілений між процесами, але завжди призводить до системного виклику до ядра, яке має деякий накладні витрати.

Критичні розділи можна використовувати лише в межах одного процесу, але мають ту перевагу, що вони переходять у режим ядра лише у випадку суперечки - Безперебійне придбання, що повинно бути звичайним випадком, надзвичайно швидко. У разі суперечки вони вводять ядро, щоб чекати на якийсь примітив синхронізації (наприклад, подію чи семафор).

Я написав швидкий зразок програми, який порівнює час між ними. У моїй системі на 1 000 000 безперебійних придбань та випусків, мютекс займає одну секунду. Критичний розділ займає ~ 50 мс на 1 000 000 придбань.

Ось код тесту, я запустив це і отримав подібні результати, якщо mutex є першим чи другим, тому інших ефектів ми не бачимо.

HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;

// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    EnterCriticalSection(&critSec);
    LeaveCriticalSection(&critSec);
}

QueryPerformanceCounter(&end);

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    WaitForSingleObject(mutex, INFINITE);
    ReleaseMutex(mutex);
}

QueryPerformanceCounter(&end);

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

printf("Mutex: %d CritSec: %d\n", totalTime, totalTimeCS);

1
Не впевнений, стосується це чи ні (оскільки я не компілював і не пробував ваш код), але я виявив, що виклик WaitForSingleObject з INFINITE призводить до низької продуктивності. Передача цього значення тайм-ауту 1, а потім циклічна перевірка його повернення зробила величезну різницю у виконанні деяких моїх кодів. Це здебільшого в контексті очікування зовнішньої обробки процесу, однак ... Не мютекс. YMMV. Мені було б цікаво побачити, як працює mutex з цією модифікацією. Отримана різниця у часі від цього тесту здається більшою, ніж слід було очікувати.
Troy Howard

5
@TroyHoward - це ви, в основному, не просто обертаєте блокування?
dss539

Причини такого розмежування є пробалами переважно історичними. Не важко реалізувати блокування, яке є таким же швидким, як CriticalSection, у беззаперечному випадку (мало атомних інструкцій, відсутні систематичні виклики), але воно працює в різних процесах (з частиною спільної пам'яті). Див., Наприклад, Linux-файли .
regnarg

2
@TroyHoward спробуйте змусити ваш процесор працювати на 100% весь час і побачити, чи працює INFINITE краще. Стратегія живлення може тривати 40 хвилин на моїй машині (Dell XPS-8700), щоб повзати назад на повну швидкість після того, як вона вирішить загальмувати, що може не зробити, якщо ви спите або чекаєте лише мілісекунди.
Стівенс Міллер

Я не впевнений, що розумію, що тут демонструється. Як правило, для вступу в критичну секцію потрібно придбати якийсь семафор. Ви хочете сказати, що поза кадром O / S має ефективний спосіб реалізувати цю критичну поведінку розділу, не вимагаючи мутексів?
SN

89

З теоретичної точки зору критичний розділ - це фрагмент коду, який не повинен працювати відразу декількома потоками, оскільки код отримує доступ до спільних ресурсів.

Взаємне блокування являє собою алгоритм (а іноді і ім'я структури даних) , який використовується для захисту критичних секцій.

Семафори та Монітори є загальною реалізацією мутексу.

На практиці існує багато застосувань mutex, доступних у Windows. Вони в основному різняться як наслідок їх виконання рівнем замикання, масштабами, витратами та ефективністю в різних рівнях суперечок. Див. Розділ CLR Inside Out - Використання паралельності для масштабованості для діаграми витрат на різні реалізації mutex.

Доступні примітиви синхронізації.

Оператор lock(object)реалізований за допомогою Monitor- див. MSDN для довідки.

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


Бачачи прийняту відповідь, я думав, може, я згадав поняття критичних розділів неправильно, поки не побачив тієї теоретичної перспективи, яку ви написали. :)
Аніруд Рамананат

2
Практичне програмування без блокування схоже на Shangri La, за винятком того, що воно існує. Кейр Фрейзер паперу (PDF) досліджує це досить цікаво (повертаючись до 2004 року). І ми все ще боремося з цим у 2012 році. Ми смокчемо.
Тім Пост

22

На додаток до інших відповідей, наступні деталі стосуються критичних розділів Windows:

  • за відсутності суперечок придбання критичного перерізу так само просто, як і InterlockedCompareExchangeоперація
  • критична структура секції містить місце для мютексу. Спочатку вона нерозподілена
  • якщо між потоками є критичний розріз, мютекс буде розподілений та використаний. Продуктивність критичної секції буде погіршуватися до продуктивності мютексу
  • якщо ви передбачаєте високі суперечки, ви можете виділити критичний розділ із зазначенням кількості віджиму.
  • якщо є суперечка на критичній секції з числом віджиму, нитка, що намагається придбати критичну секцію, буде крутитися (зайнято-чекати) протягом багатьох циклів процесора. Це може призвести до кращої продуктивності, ніж до сну, оскільки кількість циклів виконання контекстного перемикання на інший потік може бути набагато більшою, ніж кількість циклів, взятих власником потоку, щоб звільнити мютекс
  • якщо кількість віджиму закінчиться, мютекс буде виділений
  • коли власний потік звільняє критичний розділ, потрібно перевірити, чи виділено mutex, якщо він є, то він встановить mutex, щоб звільнити очікування, що очікує

У Linux я вважаю, що у них є "фіксація спина", яка служить аналогічною для критичної секції з кількістю віджиму.


На жаль, критичний розділ Window передбачає виконання операції CAS в режимі ядра , що значно дорожче, ніж фактично заблокована операція. Також у критичних розділах Windows можуть бути пов’язані кількість спинів.
Обіцяйте

2
Це однозначно неправда. CAS можна зробити за допомогою cmpxchg в режимі користувача.
Майкл

Я вважав, що за замовчуванням кількість спинів дорівнює нулю, якщо ви викликаєте InitializeCriticalSection - вам потрібно викликати InitializeCriticalSectionAndSpinCount, якщо ви хочете застосувати кількість спину. У вас є посилання на це?
1800 р. ІНФОРМАЦІЯ

18

Критичний розділ та Mutex не є операційною системою, їх концепція багатопотокової / багатопроцесорної обробки.

Критичний розділ - це фрагмент коду, який повинен виконувати лише сам у будь-який момент часу (наприклад, 5 потоків одночасно працює і функція під назвою "критична_секція_функція", яка оновлює масив ... вам не потрібно всіх 5 потоків одразу оновлення масиву. Отож, коли програма працює критично_секція_функція (), жоден з інших потоків не повинен запускати свою критичну_секцію_функції.

mutex * Mutex - це спосіб реалізації критичного коду розділу (думайте про це як маркер ... у потоці має бути володіння ним для запуску критичного_секції_коду)


2
Також мутекси можуть бути спільними для різних процесів.
конфігуратор

14

Мутекс - це об'єкт, який нитка може придбати, заважаючи іншим потокам отримувати його. Це дорадчий, не обов'язковий характер; потік може використовувати ресурс, який представляє mutex, не отримуючи його.

Критичний розділ - це довжина коду, яка гарантується, що операційна система не перебиватиметься. У псевдокоді це було б так:

StartCriticalSection();
    DoSomethingImportant();
    DoSomeOtherImportantThing();
EndCriticalSection();

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

1
Я не знаю, чому ти знявся. Існує концепція критичного розділу, який ви описали правильно, який відрізняється від об'єкта ядра Windows під назвою CriticalSection, який є типом мутексу. Я вважаю, що ОП запитувала про останнє визначення.
Адам Розенфельд

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

Ну, він попросив якомога більше деталей, і конкретно сказав, що Windows та Linux так звучать, як поняття хороші. +1 - також не зрозумів -1: /
Джейсон Коко

14

"Швидка" Windows, що дорівнює критичному вибору в Linux, буде футекс , який означає швидкий mutex простору користувачів. Різниця між futex та mutex полягає в тому, що з futex ядро ​​втягується лише тоді, коли потрібен арбітраж, тому ви зберігаєте накладні розмови з ядром щоразу, коли атомний лічильник змінюється. Це .. може заощадити значну кількість часу на узгодження блокувань у деяких програмах.

Футекс також можна поділити між процесами, використовуючи засоби, які ви б використали для спільного використання мютексу.

На жаль, футекси можуть бути дуже складними для впровадження (PDF). (Оновлення 2018 року, вони не так страшні, як у 2009 році).

Крім того, він майже однаковий на обох платформах. Ви робите атомні, марковані оновлення спільної структури таким чином, що (сподіваємось) не викликає голоду. Залишається просто метод досягнення цього.


6

У Windows критичний розділ є локальним для вашого процесу. Мутекс можна поділитись / отримати доступ до нього в усіх процесах. В основному критичні розділи значно дешевші. Не можу коментувати Linux спеціально, але в деяких системах вони просто псевдоніми для одного і того ж.


6

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

ntdll! _RTL_CRITICAL_SECTION
   + 0x000 DebugInfo: Ptr32 _RTL_CRITICAL_SECTION_DEBUG
   + 0x004 LockCount: Int4B
   + 0x008 рекурсійний кількість: Int4B
   + 0x00c Влаштування Нитка: Ptr32 недійсна
   + 0x010 LockSemaphore: Ptr32 void
   + 0x014 SpinCount: Uint4B

Тоді як mutex - це об’єкти ядра (ExMutantObjectType), створені в каталозі об'єктів Windows. Операції Mutex здебільшого реалізуються в режимі ядра. Наприклад, створюючи Mutex, ви закінчуєте викликати nt! NtCreateMutant в ядрі.


Що відбувається, коли програма, яка ініціалізує та використовує об'єкт Mutex, виходить з ладу? Чи автоматично розміщується об'єкт Mutex? Ні, я б сказав. Правильно?
Анкур

6
Об'єкти в ядрі мають відліку. Закриття ручки для об'єкта зменшує кількість відліку, і коли він досягає 0, об'єкт звільняється. Коли процес виходить з ладу, всі його ручки автоматично закриваються, тому мютекс, для якого є лише цей процес, буде автоматично розміщений.
Майкл

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

2

Чудова відповідь від Майкла. Я додав третій тест для класу mutex, введеного в C ++ 11. Результат є дещо цікавим і досі підтримує його оригінальне схвалення об'єктів CRITICAL_SECTION для окремих процесів.

mutex m;
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;

// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    EnterCriticalSection(&critSec);
    LeaveCriticalSection(&critSec);
}

QueryPerformanceCounter(&end);

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    WaitForSingleObject(mutex, INFINITE);
    ReleaseMutex(mutex);
}

QueryPerformanceCounter(&end);

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
m.lock();
m.unlock();

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    m.lock();
    m.unlock();
}

QueryPerformanceCounter(&end);

int totalTimeM = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);


printf("C++ Mutex: %d Mutex: %d CritSec: %d\n", totalTimeM, totalTime, totalTimeCS);

Мої результати були 217, 473 та 19 (зауважте, що моє співвідношення разів за останні два приблизно порівняно з Майклом, але моя машина щонайменше на чотири роки молодша за його, тому ви можете бачити докази збільшення швидкості між 2009 та 2013 роками , коли вийшов XPS-8700). Новий клас mutex удвічі швидший за мутекс Windows, але все ж менший, ніж на десяту частину швидкості об’єкта Windows CRITICAL_SECTION. Зауважте, що я протестував лише нерекурсивний мютекс. Об'єкти CRITICAL_SECTION є рекурсивними (одна нитка може вводити їх повторно, за умови, що вона залишає однакову кількість разів).


0

Функції змінного струму називаються реєнтними, якщо він використовує лише фактичні параметри.

Функції повторного виклику можуть викликатись декількома потоками одночасно.

Приклад функції рецензії:

int reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   return c;
}

Приклад невідповідної функції:

int result;

void non_reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   result = c;

}

Стандартна бібліотека strtok () не є ретентованною і не може бути використана одночасно двома або більше потоками.

Деякі SDK платформи постачаються з ретентованною версією strtok (), що називається strtok_r ();

Енріко Мільйоре

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.