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

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