В чем разница между мьютексом и критическим разделом?


134

Пожалуйста, объясните с точки зрения Linux, Windows?

Я программирую на C #, будут ли эти два термина иметь значение. Пожалуйста, постите как можно больше, с примерами и тому подобным ....

Спасибо

Ответы:


232

Для Windows критические секции легче, чем мьютексы.

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

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

Я написал быстрый пример приложения, в котором сравнивается время между ними. В моей системе для 1 000 000 несанкционированных приобретений и выпусков мьютекс занимает более одной секунды. Критическая секция занимает ~ 50 мс на 1000000 приобретений.

Вот тестовый код, я запустил его и получил аналогичные результаты, если мьютекс первый или второй, поэтому других эффектов мы не видим.

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 работает с этой модификацией. Результирующая разница во времени от этого теста кажется больше, чем следовало ожидать.
— Трой Ховард

5
@ ТройХовард, разве ты не просто блокируешь вращение в этот момент?
— dss539

Причины этого различия, скорее всего, исторические. Нетрудно реализовать блокировку, которая является такой же быстрой, как CriticalSection, в неконтролируемом случае (мало атомарных инструкций, никаких системных вызовов), но работает в разных процессах (с частью общей памяти). Смотрите, например, Linux-фьютексы .
— Regnarg

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

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

89

С теоретической точки зрения критический раздел - это фрагмент кода, который не должен выполняться несколькими потоками одновременно, поскольку код обращается к общим ресурсам.

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

Семафоры и мониторы являются распространенными реализациями мьютекса.

На практике существует много реализаций мьютекса в Windows. В основном они различаются в результате своей реализации уровнем блокировки, областями их действия, стоимостью и эффективностью при разных уровнях конкуренции. См. CLR Inside Out - Использование параллелизма для масштабируемости для диаграммы затрат на различные реализации мьютекса.

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

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

В последние годы много исследований делается на неблокирующую синхронизацию . Цель состоит в том, чтобы реализовать алгоритмы без блокировки или без ожидания. В таких алгоритмах процесс помогает другим процессам завершить свою работу, чтобы процесс мог окончательно завершить свою работу. В результате процесс может завершить свою работу, даже если другие процессы, которые пытались выполнить какую-либо работу, зависают. Используя блокировки, они не снимают свои блокировки и не позволяют другим процессам продолжаться.


Увидев принятый ответ, я подумал, что, может быть, я помнил концепцию критических секций неправильно, пока не увидел ту теоретическую перспективу, которую вы написали. :)
— Анируд Раманатан

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

22

В дополнение к другим ответам, следующие детали относятся к критическим разделам на окнах:

  • в отсутствие разногласий получение критического раздела так же просто, как InterlockedCompareExchangeоперация
  • структура критического сечения имеет место для мьютекса. Первоначально нераспределенный
  • если существует конфликт между потоками за критическую секцию, мьютекс будет выделен и использован. Производительность критической секции ухудшится до производительности мьютекса
  • если вы ожидаете высокую конкуренцию, вы можете выделить критическую секцию, указав число вращений.
  • если существует конфликт в критической секции с числом оборотов, поток, пытающийся получить критическую секцию, будет вращаться (ожидание занято) в течение такого количества циклов процессора. Это может привести к лучшей производительности, чем спящий режим, поскольку число циклов для переключения контекста на другой поток может быть намного больше, чем число циклов, выполняемых потоком-владельцем для освобождения мьютекса.
  • если счетчик спинов истекает, мьютекс будет выделен
  • когда поток-владелец освобождает критическую секцию, необходимо проверить, выделен ли мьютекс, если это так, он установит мьютекс для освобождения ожидающего потока

В Linux, я думаю, что они имеют «спин-блокировку», которая служит для целей, аналогичных критической секции с счетчиком спинов.


К сожалению, критическая секция Window включает в себя выполнение операции CAS в режиме ядра , что значительно дороже, чем фактическая операция блокировки. Кроме того, критические секции Windows могут иметь количество вращений, связанных с ними.
— Promit

2
Это определенно не правда. CAS можно сделать с помощью cmpxchg в режиме пользователя.
— Майкл

Я думал, что счетчик вращений по умолчанию равен нулю, если вы вызвали InitializeCriticalSection - вы должны вызвать InitializeCriticalSectionAndSpinCount, если вы хотите применить счетчик вращений. У вас есть ссылка на это?
— 18:00 ИНФОРМАЦИЯ

18

Critical Section и Mutex не зависят от операционной системы, их концепции многопоточности / многопроцессорности.

Критическая секция - это фрагмент кода, который должен запускаться только самостоятельно в любой момент времени (например, одновременно запущено 5 потоков и функция «variable_section_function», которая обновляет массив ... вам не нужны все 5 потоков обновлять массив сразу. Поэтому, когда программа запускает crit_section_function (), ни один из других потоков не должен запускать свою критическую функцию_секции.

mutex * Mutex - это способ реализации кода критической секции (думайте о нем, как о токене ... поток должен обладать им, чтобы запускать критический_секционный_код)


2
Также мьютексы могут быть общими для всех процессов.
— Конфигуратор

14

Мьютекс - это объект, который может получить поток, не позволяя другим потокам получить его. Это консультативный, а не обязательный; поток может использовать ресурс, который представляет мьютекс, не получая его.

Критическая секция - это длина кода, гарантируемая операционной системой, чтобы он не прерывался. В псевдокоде это будет выглядеть так:

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

1
Я думаю, что автор говорил о примитивах синхронизации пользовательского режима, таких как объект критической секции win32, который просто обеспечивает взаимное исключение. Я не знаю о Linux, но в ядре Windows есть критические области, которые ведут себя так, как вы описали, - без перерыва.
— Майкл

1
Я не знаю, почему ты получил отрицательный голос. Есть концепция критического раздела, который вы правильно описали, который отличается от объекта ядра Windows, называемого CriticalSection, который является типом мьютекса. Я считаю, что ФП спрашивал о последнем определении.
— Адам Розенфилд

По крайней мере, меня смутил языковой тег агностика. Но в любом случае это то, что мы получаем для Microsoft, называя их реализацию так же, как их базовый класс. Плохая практика кодирования!
— Микко Рантанен

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

14

«Быстрый» Windows, равный критическому выбору в Linux, был бы futex , что означает быстрый мьютекс пространства пользователя. Разница между 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 RecursionCount: Int4B
   + 0x00c OwningThread: Ptr32 Void
   + 0x010 LockSemaphore: Ptr32 Void
   + 0x014 SpinCount: Uint4B

Тогда как мьютекс - это объекты ядра (ExMutantObjectType), созданные в каталоге объектов Windows. Операции мьютекса в основном реализованы в режиме ядра. Например, при создании Mutex вы в конечном итоге вызываете nt! NtCreateMutant в ядре.


Что происходит, когда программа, которая инициализирует и использует объект Mutex, аварийно завершает работу? Получается ли объект Mutex автоматически освобожден? Нет, я бы сказал. Правильно?
— Анкур

6
Объекты ядра имеют счетчик ссылок. Закрытие дескриптора объекта уменьшает счетчик ссылок, и когда он достигает 0, объект освобождается. Когда происходит сбой процесса, все его дескрипторы автоматически закрываются, поэтому мьютекс, дескриптор которого есть только у этого процесса, будет автоматически освобожден.
— Майкл

И это причина того, что объекты Critical Section связаны с процессами, с другой стороны, мьютексы могут совместно использоваться процессами.
— Sisir

2

Отличный ответ от Майкла. Я добавил третий тест для класса мьютекса, представленного в 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 вышел). Новый класс мьютекса в два раза быстрее мьютекса Windows, но все же меньше, чем в десятую часть скорости объекта Windows CRITICAL_SECTION. Обратите внимание, что я тестировал только нерекурсивный мьютекс. Объекты CRITICAL_SECTION являются рекурсивными (один поток может вводить их повторно, при условии, что он выходит одинаковое количество раз).


0

Функции AC называются реентерабельными, если они используют только свои фактические параметры.

Повторно входящие функции могут вызываться несколькими потоками одновременно.

Пример реентерабельной функции:

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;

}

Стандартная библиотека C strtok () не является реентерабельной и не может использоваться двумя или более потоками одновременно.

Некоторые SDK для платформ поставляются с возвращающейся версией strtok (), называемой strtok_r ();

Энрико Мильоре

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.