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

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

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

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

Значит так:

  1. Создаем функцию для потока, которая должна возвращать void и принимать в качестве аргументов void* (это для случая с _beginthread);
  2. В главном потоке(или в любом другом) с помощью _beginthread запускам экземпляры потоков;

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

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

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

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

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

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

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

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

 

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

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

Подытожим:

  1. Создаем для каждого нужного объекта свою критическую секцию;
  2. Инициализируем секции перед тем, как работать с ними;
  3. Для того, что бы начать работу с объектом вызываем функцию EnterCriticalSection и передаем ей связанную(абстрактно) с объектом критическую секцию;
  4. После окончания работы с объектом, выходим из необходимой критической секции с помощью функции LeaveCriticalSection;
  5. Когда уже точно известно, что потоки больше не будут использовать критическую секцию, можем ее удалить с помощью функции DeleteCriticalSection.
  6. ПРОФИТ!

Удачи!

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

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