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


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.