Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






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






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

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

VOID InitializeCriticalSection(

LPCRITICAL_SECTION lpCriticalSection // critical section

);

В качестве параметра этой функции передается указатель на наш объект.

Перед началом работы с ресурсом каждый поток должен вызвать функцию EnterCriticalSection:

VOID EnterCriticalSection(

LPCRITICAL_SECTION lpCriticalSection // critical section

);

Завершив работу с ресурсом необходимо вызвать функцию LeaveCriticalSection:

VOID LeaveCriticalSection(

LPCRITICAL_SECTION lpCriticalSection // critical section

);

Объект критическая секция запоминает, какой поток захватил его, вызвав EnterCriticalSection. Если после этого другой поток попытается захватить тот же объект, то вызванная функция EnterCriticalSection увидит, что объект занят и переведет поток в состояние ожидания до тех пор, пока критическая секция не освободится. Вызвав функцию LeaveCriticalSection, поток освобождает критическую секцию, давая, тем самым, возможность выполнится (или захватить ее позднее) одному из ожидающих потоков.

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

Следует соблюдать осторожность при работе с несколькими критическими секциями, чтобы не создать возможность возникновения deadlock’ов. Рассмотрим два потока, которые захватывают критические секции A и B в разном порядке:

Поток 1 Поток 2
EnterCriticalSection(& A);   EnterCriticalSection(& B);   …   EnterCriticalSection(& B);   EnterCriticalSection(& A); …

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

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

VOID DeleteCriticalSection(

LPCRITICAL_SECTION lpCriticalSection // critical section

);

Таким образом, обычный порядок работы с критической секцией выглядит следующим образом:

// создаем переменную - критическую секцию

CRITICAL_SECTION cs;

 

void main(void)

{

// перед началом работы инициализируем критическую секцию

InitializeCriticalSection(& cs);

 

// работа программы

 

// в конце освобождаем ресурсы

 

DeleteCriticalSection(& cs);

}

DWORD WINAPI SomeThreadProc(LPVOID Param)

{

// в функции потока используем критическую секцию

EnterCriticalSection(& cs);

 

// работаем с защищенным ресурсом в монопольном режиме

 

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

LeaveCriticalSection(& cs);

}

Основной проблемой использования функции EnterCriticalSection является то, что ей нельзя указать время ожидания, и она может заблокировать поток на бесконечно долгое время. В Windows не предусмотрено функции, которая бы позволяла ожидать освобождения критической секции установленное время, однако есть функция, которая позволяет проверить, свободна или занята критическая секция. Это функция TryEnterCriticalSection:

BOOL TryEnterCriticalSection(

LPCRITICAL_SECTION lpCriticalSection // critical section

);

Если критическая секция занята, то функция немедленно вернет значение FALSE. Если же критическая секция свободна, то TryEnterCriticalSection захватывает критическую секцию и возвращает TRUE (в этом случае можно сразу начинать использовать защищенный ресурс, вызвать EnterCriticalSection не надо).






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.