- •КУрсовая работа
- •1 Критические секции, интервалы, ресурсы
- •1.1 Введение
- •1.2 Понятие критической секции и интервала
- •1.3 Работа с критическими секциями
- •2.3 Листинг процедур, обеспечивающих решение задачи
- •1.4 Вывод
- •2 Задача об обедающих философах
- •2.1 Постановка задачи
- •2.2 Сеть Петри
- •2.4 Вывод
- •Список используемых источников
1.3 Работа с критическими секциями
Что же происходит внутри критических секций и как они устроены? Прежде всего, следует отметить, что критические секции – это не объекты ядра операционной системы. Практически вся работа с критическими секциями происходит в создавшем их процессе. Из этого следует, что критические секции могут быть использованы только для синхронизации в пределах одного процесса. Теперь рассмотрим критические секции поближе.
Структура RTL_CRITICAL_SECTION
typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // Используется операционной системой LONG LockCount; // Счетчик использования этой критической секции LONG RecursionCount; // Счетчик повторного захвата из потока-владельца HANDLE OwningThread; // Уникальный ID потока-владельца HANDLE LockSemaphore; // Объект ядра используемый для ожидания ULONG_PTR SpinCount; // Количество холостых циклов перед вызовом ядра } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION; |
Поле LockCount увеличивается на единицу при каждом вызове ::EnterCriticalSection() и уменьшается при каждом вызове ::LeaveCriticalSection(). Это первая (а часто и единственная проверка) на пути к "захвату" критической секции. Если после увеличения в этом поле находится ноль, это означает, что до этого момента непарных вызовов ::EnterCriticalSection() из других потоков не было. В этом случае можно забрать данные, охраняемые этой критической секцией в монопольное пользование. Таким образом, если критическая секция интенсивно используется не более чем одним потоком, ::EnterCriticalSection() практически вырождается в ++LockCount, а ::LeaveCriticalSection() в --LockCount. Это очень важно. Это означает, что использование многих тысяч критических секций в одном процессе не повлечет значительного расхода ни системных ресурсов, ни процессорного времени.
В поле RecursionCount хранится количество повторных вызовов ::EnterCriticalSection() из одного и того же потока. Действительно, если вызвать ::EnterCriticalSection() из одного и того же потока несколько раз, все вызовы будут успешны. Т.е. вот такой код не остановится навечно во втором вызове ::EnterCriticalSection(), а отработает до конца.
// Поток №1 void Proc1() { ::EnterCriticalSection(&m_lock); // ... Proc2() // ... ::LeaveCriticalSection(&m_lock); }
// Все еще поток №1 void Proc2() { ::EnterCriticalSection(&m_lock); // ... ::LeaveCriticalSection(&m_lock); } |
Действительно, критические секции предназначены для защиты данных от доступа из нескольких потоков. Многократное использование одной и той же критической секции из одного потока не приведет к ошибке. Это вполне нормальное явление. Следите, чтобы количество вызовов ::EnterCriticalSection() и ::LeaveCriticalSection() совпадало, и все будет хорошо.
Поле OwningThread содержит 0 для никем не занятых критических секций или уникальный идентификатор потока-владельца. Это поле проверяется, если при вызове ::EnterCriticalSection() поле LockCount после увеличения на единицу оказалось больше нуля. Если OwningThread совпадает с уникальным идентификатором текущего потока, то RecursionCount просто увеличивается на единицу и ::EnterCriticalSection() возвращается немедленно. Иначе ::EnterCriticalSection() будет дожидаться, пока поток, владеющий критической секцией, не вызовет ::LeaveCriticalSection() необходимое количество раз.
Поле LockSemaphore используется, если нужно подождать, пока критическая секция освободится. Если LockCount больше нуля, и OwningThread не совпадает с уникальным идентификатором текущего потока, то ждущий поток создает объект ядра (событие) и вызывает ::WaitForSingleObject(LockSemaphore). Поток-владелец, после уменьшения RecursionCount, проверяет его, и если значение этого поля равно нулю, а LockCount больше нуля, то это значит, что есть как минимум однин поток, ожидающий, пока LockSemaphore не окажется в состоянии "случилось!". Для этого поток-владелец вызывает ::SetEvent(), и какой-то один (только один) из ожидающих потоков пробуждается и получает доступ к критическим данным.
И, наконец, поле SpinCount. Это поле используется только многопроцессорными системами. В однопроцессорных системах, если критическая секция занята другим потоком, можно только переключить управление на нее и подождать наступления события. В многопроцессорных системах есть альтернатива: прогнать некоторое количество раз холостой цикл, проверяя каждый раз, не освободилась ли критическая секция. Если за SpinCount раз это не получилось, переходим к ожиданию. Это гораздо эффективнее, чем переключение на планировщик ядра и обратно.[2]
