В чем разница между мьютексом и критическим сектором?
Пожалуйста, объясните с Linux, перспективы Windows?
Я программирую на С#, будут ли эти два условия иметь значение. Пожалуйста, напишите как можно больше, с примерами и т.д.
Спасибо
Ответы
Ответ 1
Для Windows критические разделы имеют меньший вес, чем мьютексы.
Мьютексы могут быть разделены между процессами, но всегда приводят к системному вызову ядра, которое имеет некоторые накладные расходы.
Критические разделы могут использоваться только в рамках одного процесса, но имеют то преимущество, что они только переключаются в режим ядра в случае разногласий. Необычные приобретения, которые должны быть обычным делом, невероятно быстры. В случае разногласий они входят в ядро, чтобы ждать некоторого примитива синхронизации (например, событие или семафор).
Я написал приложение для быстрого примера, которое сравнивает время между ними. В моей системе на 1,000,000 необоснованных приобретений и релизов мьютекс занимает одну секунду. Критическая секция занимает ~ 50 мс для 1,000,000 приобретений.
Вот тестовый код, я запустил его и получил аналогичные результаты, если мьютекс первый или второй, поэтому мы не видим никаких других эффектов.
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);
Ответ 2
С теоретической точки зрения критический раздел является частью кода, который не должен запускаться сразу несколькими потоками, потому что код обращается к совместно используемому ресурсы.
A mutex - это алгоритм (а иногда и название структуры данных), который используется для защиты критических разделов.
Семафоры и Мониторы являются общими реализациями мьютекса.
На практике существует много реализаций mutex, доступных в окнах. Они в основном отличаются, как следствие их реализации, их уровнем блокировки, их областями, их расходами и их производительностью при разных уровнях конкуренции. См. CLR Inside Out -
Используя concurrency для масштабируемости для диаграммы затрат на различные реализации мьютексов.
Доступные примитивы синхронизации.
Оператор lock(object)
реализуется с помощью Monitor
- см. MSDN для справки.
В последние годы большое количество исследований проводится по неблокирующей синхронизации. Цель состоит в том, чтобы реализовать алгоритмы без блокировки или без ожидания. В таких алгоритмах процесс помогает другим процессам завершить свою работу, чтобы процесс мог окончательно завершить свою работу. В результате процесс может завершить свою работу, даже если другие процессы, которые пытались выполнить какую-то работу, зависают. Usinig блокирует, они не будут освобождать свои блокировки и предотвращать продолжение других процессов.
Ответ 3
Критический раздел и Mutex не являются специфичными для операционной системы, их понятиями многопоточности/многопроцессорности.
Критический раздел
Является ли часть кода, которая должна выполняться только ею в любой момент времени (например, существует 5 потоков, работающих одновременно, и функция под названием "critical_section_function", которая обновляет массив... вы не хотите, чтобы все 5 потоков обновляли массив сразу. Поэтому, когда в программе выполняется критическая_секция_функции(), ни один из других потоков не должен запускать свою функцию critical_section_function.
мьютекс *
Mutex - это способ реализации кода критического раздела (подумайте об этом как токен... поток должен обладать им для запуска критического_сезонного_кода)
Ответ 4
В дополнение к другим ответам, следующие детали относятся к критическим разделам в окнах:
- в отсутствие конкуренции, получение критического раздела так же просто, как операция
InterlockedCompareExchange
- структура критического сечения содержит место для мьютекса. Он изначально нераспределен.
- Если есть конфликт между потоками для критического раздела, мьютекс будет выделен и использован. Производительность критического раздела будет ухудшаться до производительности мьютекса
- Если вы ожидаете высокой конкуренции, вы можете выделить критический раздел, определяющий количество оборотов.
- если есть конфликт в критической секции со счетчиком спинов, поток, пытающийся получить критический раздел, будет вращаться (ожидание-ожидание) для многих циклов процессора. Это может привести к повышению производительности, чем спящий режим, поскольку количество циклов для переключения контекста в другой поток может быть намного выше, чем количество циклов, выполняемых потоком владения, для освобождения мьютекса
- если счетчик спинов истекает, мьютекс будет выделен
- когда личный поток освобождает критическую секцию, требуется проверить, выделен ли мьютекс, если это тогда, то он установит мьютекс для освобождения ожидающего потока
В linux я думаю, что у них есть "блокировка спина", которая аналогично относится к критическому разделу со счетчиком спинов.
Ответ 5
Мьютекс - это объект, который может получить поток, не позволяя другим потокам его приобретать. Он является консультативным, а не обязательным; поток может использовать ресурс, который представляет собой мьютекс, не приобретая его.
Критический раздел - это длина кода, которая гарантируется операционной системой, чтобы не быть перепутаной. В псевдокоде это будет выглядеть так:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
Ответ 6
"Быстрая" равность Windows для критического выбора в Linux будет futex, что означает мьютекс быстрого пространства пользователя. Разница между futex и мьютексом заключается в том, что с futex ядро становится задействованным только тогда, когда требуется арбитраж, поэтому вы сохраняете накладные расходы на разговоры с ядром каждый раз, когда изменяется атомный счетчик. Futex также может быть разделен между процессами, используя средства, которые вы использовали для обмена мьютексом.
К сожалению, futexes может быть очень сложно реализовать (PDF).
Кроме того, это почти одинаково на обеих платформах. Вы делаете атомарные обновления с токеном в общую структуру таким образом, что (надеюсь) не вызывает голодания. Остается просто метод выполнения этого.
Ответ 7
В Windows критический раздел является локальным для вашего процесса. Мьютекс можно обменивать/просматривать через процессы. В принципе, критические разделы намного дешевле. Не могу комментировать Linux конкретно, но в некоторых системах они просто псевдонимы для одного и того же.
Ответ 8
Просто чтобы добавить мои 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 в основном реализуются в режиме ядра. Например, при создании Mutex вы вызываете nt! NtCreateMutant в ядре.
Ответ 9
Отличный ответ от Майкла. Я добавил третий тест для класса mutex, введенного в С++ 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 являются рекурсивными (один поток может вводить их повторно, при условии, что он оставляет одно и то же число раз).