Critical section что это

FreeRTOS: мьютексы и критические секции

Мьютексы.

Critical section что это. Смотреть фото Critical section что это. Смотреть картинку Critical section что это. Картинка про Critical section что это. Фото Critical section что это

Critical section что это. Смотреть фото Critical section что это. Смотреть картинку Critical section что это. Картинка про Critical section что это. Фото Critical section что это

Critical section что это. Смотреть фото Critical section что это. Смотреть картинку Critical section что это. Картинка про Critical section что это. Фото Critical section что это

Critical section что это. Смотреть фото Critical section что это. Смотреть картинку Critical section что это. Картинка про Critical section что это. Фото Critical section что это

Рисунок 4. После окончания работы таск В возвращает жетон и в результате другие таски могут получить доступ к ресурсу.
Для создания мьютекса используется специальная API функция:

Которая возвращает созданный мьютекс, или NULL, если недостаточно памяти.
Следующий код показывает механизм работы с мьютексом:

Критические секции.

Что если по ходу работы логики Вашего кода, необходим короткий участок кода, выполнение которого не должно быть прервано переключением на другой таск (примером такого участка может послужить запись какого-либо значения в порт). Для этого в FreeRTOS используются специальные макросы:

Во время выполнения этого участка кода не может быть произведено переключение контекста (т.е. переключение на другую задачу), но могут приходить прерывания на версиях FreeRTOS с поддержкой вложенных прерываний, но не все, а только те у которых приоритет выше константы configMAX_SYSCALL_INTERRUPT_PRIORITY.

Другой подход построения критических секций — приостановка работы планировщика. Разница лишь в том, что критическая секция, построенная на макросах, защищает выполняемый участок кода от совместного доступа других тасков, и прерываний, а критическая секция, построенная на приостановке планировщика — только от других тасков.
Для этого используют 2 API функции:

При этом значение, которое возвращает функция xTaskResumeAll(); указывает на необходимость выполнения принудительного переключения контекста с помощью taskYIELD();

Инверсия приоритетов и тупики.

В прошлой статье я вскользь упомянул о gatekeeper task, как о решении проблемы инверсии приоритетов и попадания тасков в тупик. Здесь я бы более подробно хотел описать, что это такое.

Инверсия приоритетов — это ситуация при которой, Task A имеющий более высокий приоритет, чем Task B ожидает завершения его работы т.к. он первым захватил „жетон» (мьютекс).

Пожалуй, на этом все, интересно было бы услышать поправки по всему циклу, дабы скорректировать статьи, и в дальнейшем ссылаться на них.
Для дальнейшего изучения я бы, прежде всего, посоветовал прочитать оригинальное руководство «Using the FreeRTOS real time kernel», информация из которого использовалась при написании статей, а также документацию на официальном сайте.

Источник

Представляет объект критической секции.

Синтаксис

Члены

Конструктор

ИмяОписание
CriticalSection:: CriticalSectionИнициализирует объект синхронизации, похожий на объект Mutex, но может использоваться только потоками одного процесса.
CriticalSection::

CriticalSectionВыполняет деинициализацию и уничтожает текущий CriticalSection объект.

Открытые методы

nameОписание
CriticalSection:: IsValidУказывает, допустима ли текущая критическая секция.
CriticalSection:: LockОжидает владение заданным объектом критического раздела. Функция возвращает, когда вызывающему потоку предоставляется владение.
CriticalSection:: TryLockПытается войти в критическую секцию без блокировки. Если вызов выполнен успешно, вызывающий поток принимает владение критическим разделом.

Защищенные члены данных

Иерархия наследования

Требования

Заголовок: кореврапперс. h

Пространство имен: Программы Microsoft:: WRL:: оболочки

CriticalSection::

Выполняет деинициализацию и уничтожает текущий CriticalSection объект.

CriticalSection:: CriticalSection

Инициализирует объект синхронизации, похожий на объект Mutex, но может использоваться только потоками одного процесса.

Параметры

спинкаунт
Счетчик прокруток для объекта критической секции. Значение по умолчанию — 0.

Комментарии

дополнительные сведения о критических разделах и спинкаунтс см. в описании InitializeCriticalSectionAndSpinCount функции в Synchronization разделе Windows API документации по.

CriticalSection:: cs_

Объявляет критически важный элемент данных секции.

Комментарии

Эти данные-член защищены.

CriticalSection:: IsValid

Указывает, допустима ли текущая критическая секция.

Возвращаемое значение

CriticalSection:: Lock

Ожидает владение заданным объектом критического раздела. Функция возвращает, когда вызывающему потоку предоставляется владение.

Параметры

сложных
Определенный пользователем объект критической секции.

Возвращаемое значение

Объект блокировки, который можно использовать, чтобы разблокировать текущую критическую секцию.

Комментарии

Первая функция Lock влияет на текущий объект критической секции. Вторая функция Lock влияет на указанную пользователем критическую секцию.

CriticalSection:: TryLock

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

Параметры

сложных
Определенный пользователем объект критической секции.

Возвращаемое значение

Ненулевое значение, если критический раздел успешно указан или текущий поток уже владеет критическим разделом. Нуль, если другой поток уже владеет критическим разделом.

Комментарии

Первая функция TryLock влияет на текущий объект критической секции. Вторая функция TryLock влияет на указанную пользователем критическую секцию.

Источник

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


Автор: Павел Блудов
The RSDN Group
Источник: RSDN Magazine #6-2004

Опубликовано: 14.03.2005
Исправлено: 10.12.2016
Версия текста: 1.2

Введение

Именно тут приходят на помощь критические секции. Перепишем наш пример.

Работа с критическими секциями

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

Структура RTL_CRITICAL_SECTION

СОВЕТ

Не стоит экономить на критических секциях. Много cэкономить все равно не получится.

В поле RecursionCount хранится количество повторных вызовов ::EnterCriticalSection() из одной и той же нити. Действительно, если вызвать ::EnterCriticalSection() из одной и той же нити несколько раз, все вызовы будут успешны. Т.е. вот такой код не остановится навечно во втором вызове ::EnterCriticalSection(), а отработает до конца.

Действительно, критические секции предназначены для защиты данных от доступа из нескольких ниток. Многократное использование одной и той же критической секции из одной нити не приведет к ошибке. Это вполне нормальное явление. Следите, чтобы количество вызовов ::EnterCriticalSection() и ::LeaveCriticalSection() совпадало, и все будет хорошо.

Поле OwningThread содержит 0 для никем не занятых критических секций или уникальный идентификатор нити-владельца. Это поле проверяется, если при вызове ::EnterCriticalSection() поле LockCount после увеличения на единицу оказалось больше нуля. Если OwningThread совпадает с уникальным идентификатором текущей нити, то RecursionCount просто увеличивается на единицу и ::EnterCriticalSection() возвращается немедленно. Иначе ::EnterCriticalSection() будет дожидаться, пока нить, владеющая критической секцией, не вызовет ::LeaveCriticalSection() необходимое количество раз.

Поле LockSemaphore используется, если нужно подождать, пока критическая секция освободится. Если LockCount больше нуля, и OwningThread не совпадает с уникальным идентификатором текущей нити, то ждущая нить создает объект ядра (событие) и вызывает ::WaitForSingleObject( LockSemaphore ). Нить-владелец, после уменьшения RecursionCount, проверяет его, и если значение этого поля равно нулю, а LockCount больше нуля, то это значит, что есть как минимум одна нить, ожидающая, пока LockSemaphore не окажется в состоянии «случилось!». Для этого нить-владелец вызывает ::SetEvent(), и какая-то одна ( только одна ) из ожидающих ниток пробуждается и получает доступ к критическим данным.

WindowsNT/2k генерирует исключение, если попытка создать событие не увенчалась успехом. Это верно как для функций ::Enter/LeaveCriticalSection(), так и для ::InitializeCriticalSectionAndSpinCount() с установленным старшим битом параметра SpinCount. Но только не в WindowsXP. Разработчики ядра этой операционной системы поступили по-другому. Вместо генерации исключения, функции ::Enter/LeaveCriticalSection(), если не могут создать собственное событие, начинают использовать заранее созданный глобальный объект. Один на всех. Таким образом, в случае катастрофической нехватки системных ресурсов, программа под управлением WindowsXP ковыляет какое-то время дальше. Действительно, писать программы, способные продолжать работать после того, как ::EnterCriticalSection() сгенерировала исключение, чрезвычайно сложно. Как правило, если программистом и предусмотрен такой поворот событий, то дальше вывода сообщения об ошибке и аварийного завершения программы дело не идет. Как следствие, WindowsXP игнорирует старший бит поля LockCount.

ПРИМЕЧАНИЕ

Все это верно для Windows NT/2k/XP. В Windows 9x/Me используется только поле LockCount. Там находится указатель на объект ядра, возможно, просто взаимоисключение (mutex). Все остальные поля равны нулю.

API для работы с критическими секциями

BOOL InitializeCriticalSection (LPCRITICAL_SECTION lpCriticalSection );

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

Устанавливает значение поля SpinCount и возвращает его предыдущее значение. Напоминаю, что старший бит отвечает за «привязку» события, используемого для ожидания доступа к данной критической секции.

VOID DeleteCriticalSection (LPCRITICAL_SECTION lpCriticalSection );

Освобождает ресурсы, занимаемые критической секцией.

VOID EnterCriticalSection (LPCRITICAL_SECTION lpCriticalSection );

BOOL TryEnterCriticalSection (LPCRITICAL_SECTION lpCriticalSection );

Осуществляют «захват» критической секции. Если критическая секция занята другой нитью, то ::EnterCriticalSection() будет ждать, пока та освободится, а ::TryEnterCriticalSection() вернет FALSE. Отсутствует в Windows 9x/ME.

VOID LeaveCriticalSection (LPCRITICAL_SECTION lpCriticalSection );

Освобождает критическую секцию,

Классы-обертки для критических секций

Классы CLock и CAutoLock удобно использовать для синхронизации доступа к переменным класса, а CScopeLock предназначен, в основном, для использования в процедурах. Удобно, что компилятор сам позаботится о вызове ::LeaveCriticalSection() через деструктор.

Отладка критических секций

Весьма интересное и увлекательное занятие. Можно потратить часы и недели, но так и не найти, где именно возникает проблема. Стоит уделить этому особо пристальное внимание. Ошибки, связанные с критическими секциями, бывают двух типов: ошибки реализации и архитектурные ошибки.

Ошибки, связанные с реализацией

Это довольно легко обнаруживаемые ошибки, как правило, связанные с непарностью вызовов ::EnterCriticalSection() и ::LeaveCriticalSection().

::LeaveCriticalSection() без ::EnterCriticalSection() приведет к тому, что первый же вызов ::EnterCriticalSection() остановит выполнение нити навсегда.

В этом примере, конечно, имеет смысл воспользоваться классом типа CScopeLock.

Кроме того, случается, что ::EnterCriticalSection() вызывается без инициализации критической секции с помощью ::InitializeCriticalSection(). Особенно часто такое случается с проектами, написанными с помощью ATL. Причем в debug-версии все работает замечательно, а release-версия рушится. Это происходит из-за так называемой «минимальной» CRT (_ATL_MIN_CRT), которая не вызывает конструкторы статических объектов (Q166480, Q165076). В ATL версии 7.0 эту проблему решили.

Еще я встречал такую ошибку: программист пользовался классом типа CScopeLock, но для экономии места называл эту переменную одной буквой:

и как-то раз просто пропустил имя у переменной. Получилось

Что это означает? Компилятор честно сделал вызов конструктора CScopeLock и тут же уничтожил этот безымянный объект, как и положено по стандарту. Т.е. сразу же после вызова метода Lock() последовал вызов Unlock(), и синхронизация перестала иметь место. Вообще, давать переменным, даже локальным, имена из одной буквы – путь быстрого наступления на всяческие грабли.

СОВЕТ

Если у вас в процедуре больше одного цикла, то вместо int i,j,k стоит все-таки использовать что-то вроде int nObject, nSection, nRow.

Архитектурные ошибки

Самая известная из них – это взаимоблокировка (deadlock), когда две нити пытаются захватить две или более критических секций, причем делают это в разном порядке.

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

Из такого присвоения трудно извлечь какую-либо пользу. А вот такой код иногда пишут:

и все бы хорошо, если бы у структуры SData был конструктор копирования, например такой:

Но нет, программист посчитал, что хватит за глаза простого копирования полей, и, в результате, переменная m_lock была просто скопирована, хотя именно в этот момент из другой нити она была «захвачена», и значение поля LockCount у нее в этот момент больше либо равно нулю. После вызова ::LeaveCriticalSection() в той нити, у исходной переменной m_lock значение поля LockCount уменьшилось на единицу. А у скопированной переменной – осталось прежним. И любой вызов ::EnterCriticalSection() в этой нити никогда не вернется. Он будет вечно ждать неизвестно чего.

Это только цветочки. С ягодками вы очень быстро столкнетесь, если попытаетесь написать что-нибудь действительно сложное. Например, ActiveX-объект в многопоточном подразделении (MTA), создаваемый из скрипта, запущенного из-под контейнера, размещенного в однопоточном подразделении (STA). Ни слова не понятно? Не беда. Сейчас я попытаюсь выразить проблему более понятным языком. Итак. Имеется объект, вызывающий методы другого объекта, причем живут они в разных нитях. Вызовы производятся синхронно. Т.е. объект №1 переключает выполнение на нить объекта №2, вызывает метод и переключается обратно на свою нить. При этом выполнение нити №1 приостановлено до тех пор, пока не отработает нить объекта №2. Теперь, положим, объект №2 вызывает метод объекта №1 из своей нити. Получается, что управление вернулось в объект №1, но из нити объекта №2. Если объект №1 вызывал метод объекта №2, захватив какую-либо критическую секцию, то при вызове метода объекта №1 тот заблокирует сам себя при повторном входе в ту же критическую секцию.

Если бы в примере не было переключения нитей, все вызовы произошли бы в нити объекта №1, и никаких проблем не возникло. Сильно надуманный пример? Ничуть. Именно переключение ниток лежит в основе подразделений (apartments) COM. А из этого следует одно очень, очень неприятное правило.

СОВЕТ

Избегайте вызовов каких бы то ни было объектов при захваченных критических секциях.

Помните пример из начала статьи? Так вот, он абсолютно неприемлем в подобных случаях. Его придется переделать на что-то вроде примера, приведенного в листинге 12.

Доступ к объекту по-прежнему синхронизован, но вызов SomeMethod(); происходит вне критической секции. Победа? Почти. Осталась одна маленькая деталь. Давайте посмотрим, что происходит в Proc2():

Очевидно, что вызовы m_pObject.p->AddRef(); и m_pObject.p->Release(); происходят внутри критической секции. И если вызов метода AddRef(), как правило, безвреден, то вызов метода Release() может оказаться последним вызовом Release(), и объект самоуничтожится. В методе FinalRelease() объекта №2 может быть все что угодно, например, освобождение объектов, живущих в других подразделениях. А это опять приведет к переключению ниток и может вызвать самоблокировку объекта №1 по уже известному сценарию. Придется воспользоваться той же техникой, что и в методе Proc1():

Теперь потенциально последний вызов IObject2::Release() будет осуществлен после выхода из критической секции. А присвоение нового значения по-прежнему синхронизовано с вызовом IObject2::SomeMethod() из нити №1.

Способы обнаружения ошибок

Ну, а какие у нас альтернативы? Да, пожалуй, только одна. Не использовать API для работы с критическими секциями. Вместо них написать свои собственные. Пусть даже не такие обточенные напильником, как в Windows NT. Не страшно. Нам это понадобится только в debug-конфигурациях. В release’ах мы будем продолжать использовать оригинальный API от Майкрософт. Для этого напишем несколько функций, полностью совместимых по типам и количеству аргументов с «настоящим» API, и добавим #define, как у MFC, для переопределения оператора new в debug-конфигурациях.

Ну и заодно добавим еще один метод в наш класс Clock (листинг 15).

Использовать метод Check() в release-конфигурациях не стоит, возможно, что в будущем, в какой-нибудь Windows64, структура RTL_CRITICAL_SECTION изменится, и результат такой проверки будет не определен. Так что ему самое место «жить» внутри всяческих ASSERT’ов.

Итак, что мы имеем? Мы имеем проверку на лишний вызов ::LeaveCriticalSection() и ту же трассировку для блокировок. Не так уж много. Особенно если трассировка о блокировке имеет место, а вот нить, забывшая освободить критическую секцию, давно завершилась. Как быть? Вернее, что бы еще придумать, чтобы ошибку проще было выявить? Как минимум, прикрутить сюда __LINE__ и __FILE__, константы, соответствующие текущей строке и имени файла на момент компиляции этого метода.

Компилируем, запускаем. Результат удивительный. Хотя правильный. Компилятор честно подставил номер строки и имя файла, соответствующие началу нашей EnterCriticalSectionDbg(). Так что придется попотеть немного больше. __LINE__ и __FILE__ нужно вставить в #define’ы, тогда мы получим действительные номер строки и имя исходного файла. Теперь вопрос, куда же сохранить эти параметры для дальнейшего использования? Причем хочется оставить за собой возможность вызова стандартных функций API наряду с нашими собственными? На помощь приходит C++: просто создадим свою структуру, унаследовав ее от RTL_CRITICAL_SECTION (листинг 16).

Приводим наши классы в соответствие (листинг 17).

Сменим тему

А что это мы все про Win32 API да про C++? Давайте посмотрим, как обстоят дела с критическими секциями в более современных языках программирования.

Тут стараниями Майкрософт имеется полный набор старого доброго API под новыми именами.

А вот Monitor.TryEnter() в C# (о, чудо!) принимает в качестве параметра максимальный период ожидания.

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

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

Тут тоже появился атрибут [synchronized] ведущий себя точно так же, как и одноименное ключевое слово из Java. Странно, что архитекторы из Майкрософт решили позаимствовать синтаксис из продукта от Sun Microsystems вместо своего собственного.

Delphi

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

Подведем итоги

Итак, что нужно знать о критических секциях:

Источник

Критические секции в С++

Теперь я расскажу вам, что такое критическая секция, зачем она нужна и как ее использовать. Конечно же статья будет о многопоточном программировании и увы только под Windows. Я буду использовать Windows7 x64 + VS2010 SP1. Для начала напишем очень простое приложение в несколько потоков.

И так, функция _beginthread как вы наверно уже догадались создает новый поток ее прототип:

Параметр start_address задает функцию потока, параметр stack_size — размер стека ну и arglist — список аргументов передаваемых потоку. Если поток удачно запущен, он вернет его идентификатор(handle).

Все ну очень просто.

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

В книге Джеффри Рихтера «Windows для профессионалов», автор приводит такой пример:

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

Если у Вас есть ресурсы, всегда используемые вместе, Вы можете поместить их в одну кабинку — единственная структура CRITICAL_SECTION будет охранять их всех. Но если ресурсы не всегда используются вместе (например, потоки 1 и 2 работают с одним ресурсом, а потоки 1 и 3 — с другим), Вам придется создать им по отдельной кабинке, или структуре CRITICAL_SECTION.

Теперь в каждом участке кода, где Вы обращаетесь к разделяемому ресурсу, вызывайте EnterCriticaSection, передавая ей адрес структуры CRITICAL_SECTION, которая выделена для этого ресурса. Иными словами, поток, желая обратиться к ресурсу, должен сначала убедиться, нет ли на двери кабинки знака «занято». Структура CRITI CAL_SECTION идентифицирует кабинку, в которую хочет войти поток, а функция EnterCriticalSection — тот инструмент, с помощью которого он узнает, свободна или занята кабинка. EnterCriticalSection допустит вызвавший ее поток в кабинку, если определит, что та свободна. В ином случае (кабинка занята) EnterCriticalSection заставит его ждать, пока она не освободится.

Поток, покидая участок кода, где он работал с защищенным ресурсом, должен вызвать функцию LeaveCriticalSection. Тем самым он уведомляет систему о том, что кабинка с данным ресурсом освободилась. Если Вы забудете это сделать, система будет считать, что ресурс все еще занят, и не позволит обратиться к нему другим ждущим потокам, То есть Вы вышли из кабинки и оставили на двери знак «занято».

Давайте создадим простую программу. В ней будет массив, и доступ к нему из разных потоков.

Critical section что это. Смотреть фото Critical section что это. Смотреть картинку Critical section что это. Картинка про Critical section что это. Фото Critical section что это

Результат работы программы на изображении справа.

Как видно каждый поток по очереди изменил массив чисел, при этом никаких ошибок по поводу доступа к памяти не возникло и программа отработала корректно!

Источник

Класс critical_section

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

Синтаксис

Члены

Общедоступные определения типов

Открытые классы

ИмяОписание
Класс critical_section::scoped_lockЗащищенная от исключения оболочка RAII для critical_section объекта.

Открытые конструкторы

critical_section

Уничтожает критическую секцию.

Открытые методы

nameОписание
lockПолучает этот критический раздел.
native_handleВозвращает собственный машинный код, зависящий от платформы, если он существует.
try_lockПытается получить блокировку без блокировки.
try_lock_forПытается получить блокировку без блокировки в течение указанного числа миллисекунд.
блокированРазблокирует критическую секцию.

Комментарии

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

Иерархия наследования

Требования

Заголовок: ConcRT. h

Пространство имен: параллелизм

critical_section

Конструирует новую критическую секцию.

Уничтожает критическую секцию.

Комментарии

Ожидается, что блокировка больше не удерживается при выполнении деструктора. Разрешение критической секции, уничтожения с блокировкой, по-прежнему приводит к неопределенному поведению.

Получает этот критический раздел.

Комментарии

Часто безопаснее использовать конструкцию scoped_lock для получения и освобождения объекта в безопасном для исключения виде.

native_handle

Возвращает собственный машинный код, зависящий от платформы, если он существует.

Возвращаемое значение

Ссылка на критическую секцию.

Комментарии

critical_section объект не связан с машинным кодом, зависящим от платформы, для операционной системы Windows. Метод просто возвращает ссылку на сам объект.

Класс critical_section::scoped_lock

Защищенная от исключения оболочка RAII для critical_section объекта.

scoped_lock:: scoped_lock

Создает scoped_lock объект и получает critical_section объект, переданный в _Critical_section параметре. Если критическая секция удерживается другим потоком, этот вызов блокируется.

Параметры

_Critical_section
Критическая секция для блокировки.

scoped_lock::

Уничтожает scoped_lock объект и освобождает критическую секцию, заданную в его конструкторе.

try_lock

Пытается получить блокировку без блокировки.

Возвращаемое значение

try_lock_for

Пытается получить блокировку без блокировки в течение указанного числа миллисекунд.

Параметры

_Timeout
Количество миллисекунд перед истечением времени ожидания.

Возвращаемое значение

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *