Критическая секция

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 20:05, 28 декабря 2017.

При параллельном программировании одновременный доступ к общим ресурсам может привести к неожиданному или ошибочному поведению программы, поэтому части программы, в которых имеется доступ к общему ресурсу защищаются специальной секцией. Этот защищенная секция называется критической секцией. Она не может выполняться несколькими процессами. Как правило, критическая секция обращается к общему ресурсу, например структуре данных, периферийному устройству или сетевому соединению, которые не будут работать корректно в контексте нескольких одновременных доступов.[Источник 1]

Критическая секция

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

  1. Два процесса не должны одновременно находиться в критических областях.
  2. В программе не должно быть предположений о скорости или количестве процессоров.
  3. Процесс, находящийся вне критической области, не может блокировать другие процессы.
  4. Невозможна ситуация, в которой процесс вечно ждет попадания в критическую область.

Необходимость Критической секции

Различные коды или процессы могут иметь одну и ту же переменную или другие ресурсы, которые необходимо прочитать или записать, но результаты которых зависят от порядка, в котором происходят действия. Например, если переменная «x» должна быть прочитана процессом A, и процесс B должен записывать ту же переменную «x» в то же время, процесс A может получить либо старое, либо новое значение «x».

Процесс A:
// Process A
 .
 .
 b = x+5;                 // инструкция выполняется в момент времени = Тx
 .
Процесс B:
// Process B
.
.
x = 3+z;                 // инструкция выполняется в момент времени = Тx
.
Рисунок 1: График потоков показывает необходимость критической секции

В таких случаях важна критическая секция. В приведенном выше случае, если A необходимо прочитать обновленное значение «x», выполнение процесса A и процесса B одновременно может не дать требуемых результатов. Чтобы предотвратить это, переменная «х» защищена критической секцией. Сначала, B получает доступ к секции. Когда B заканчивает запись значения, A получает доступ к критической секции, и переменная «x» может быть прочитана.

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

Реализация критической секции

Реализация критической секции зависит от конкретной операционной системы.
Рисунок 2: Шлюзы(L) и критические секции(CS) в параллельных потоках

Критическая секция - это часть программы, которая требует взаимно исключающего доступа. Как показано на рис. 2[Источник 2]в случае взаимного исключения (Мьютекса), один поток блокирует критическую секцию, используя шлюзы, когда ему необходимо получить доступ к общему ресурсу, а другие потоки должны ждать, чтобы получить свою очередь попасть в секцию. Это предотвращает конфликты, когда два или более потока совместно используют одно и то же пространство памяти и хотят получить доступ к общему ресурсу.[Источник 3]

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

Этот грубый подход можно улучшить с помощью семафоров. Чтобы войти в критическую секцию, поток должен попасть в семафор, который он освобождает при выходе из секции. Другие потоки не могут войти в критическую секцию одновременно с исходным потоком, но могут свободно управлять процессором и выполнять другой код, включая другие критические секции, которые защищены различными семафорами. Блокировка семафора также имеет ограничение по времени, чтобы предотвратить условие взаимоблокировки, при котором шлюз занимается одним процессом в течение бесконечного времени, останавливая другие процессы, которые должны использовать общий ресурс, защищенный критической секцией.

Критические секции уровня ядра

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

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

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

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

Так как критические секции могут выполняться только на процессоре, в который они были введены, синхронизация требуется только в пределах выполняющего процессора. Это позволяет входить в и выходить из критической секции за почти нулевые затраты. Никакая межпроцессорная синхронизация не требуется, только синхронизация потока команд. Большинство процессоров обеспечивают необходимое количество синхронизации простым актом прерывания текущего режима выполнения. Это позволяет критическим секциям в большинстве случаев быть не чем счетчиком входов в критическую секцию на процессор.

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

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

Критические секции уровня ядра являются основой проблемой локаута программного обеспечения.

Критические секции в разных языках

C++

CRTITICAL_SECTION csFlag; //объявление критической секции
...
InitializeCriticalSection(&csFlag); //инициализациия критической секции
...
EnterCriticalSection(&csFlag); //функция входа в критическую секция
...
LeaveCriticalSection(&csFlag); //функция выхода из критической секции
...
DeleteCriticalSection(&csFlag); //Удаление(деинициализация) критической секции

C#

Тут имеется схожий с C++ API, но под другими именами.

Критические секции представлены классом System.Threading.Monitor, вместо ::EnterCriticalSection() есть Monitor.Enter(object), а вместо ::LeaveCriticalSection() Monitor.Exit(object), где object – это любой объект C#. Т.е. каждый объект где-то в потрохах CLR (Common Language Runtime) имеет свою собственную критическую секцию либо заводит ее по необходимости. Типичное использование этой секции выглядит так:
Monitor.Enter(this);
m_dwSmth = dwSmth;
Monitor.Exit(this);
Если нужно организовать отдельную критическую секцию для какой-либо переменной, самым логичным способом будет поместить ее в отдельный объект и использовать этот объект как аргумент при вызове Monitor.Enter/Exit(). Кроме того, в C# существует ключевое слово lock, это полный аналог нашего класса CScopeLock.
lock (this)
{
  m_dwSmth = dwSmth;
}
Monitor.TryEnter() в C# принимает в качестве параметра максимальный период ожидания.

Примечание: CLR – это не только C#, все это применимо и к другим языкам, использующим CLR.

Java

В этом языке используется подобный механизм, только место ключевого слова lock есть ключевое слово synchronized, а все остальное – точно так же.
        synchronized (this)
{
  m_dwSmth = dwSmth;
}

MC++ (управляемый C++)

Тут тоже появился атрибут [synchronized] ведущий себя точно так же, как и одноименное ключевое слово из Java. Майкрософт решили позаимствовать синтаксис из продукта от Sun Microsystems вместо своего собственного.
[synchronized] DWORD m_dwSmth;
//...
m_dwSmth = dwSmth; // неявный вызов Lock(this)

Delphi

Практически все, что верно для C++, верно и для Delphi. Критические секции представлены объектом TCriticalSection. Собственно, это такая же обертка.

Кроме того, в Delphi присутствует специальный объект TMultiReadExclusiveWriteSynchronizer с названием, говорящим само за себя.

Источники

  1. Критическая секция // Studepedia [2015-2017].Дата обновления: 23.07.2015. URL: http://studepedia.org/index.php?vol=1&post=2362 (дата обращения: 25.10.2017)
  2. Critical section // Wikipedia [1999-2017].Дата обновления: 27.10.2017. URL: https://en.wikipedia.org/wiki/Critical_section (дата обращения: 25.10.2017)
  3. Критическая секция // RSDN [2004-2017].Дата обновления: 10.12.2016. URL: https://rsdn.org/article/baseserv/critsec.xml (дата обращения: 25.10.2017)