Delphi. Потоки. Критические секции

Данная статья про потоки и критические секции. Она основана на небольших личных экспериментах и материалах сети.

Что такое критические секции?

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

15

Этот принцип положен в основу критических секций. Блокирование ресурсов под один поток в один момент времени и есть суть критической секции.

Мне понравилось одно сравнение. Есть труба, в которую может влезть только один человек. А людей, которым нужно пролезть через эту трубу скажем так несколько десятков. И вот один из них подходит к трубе, заглядывает в неё и видит, что там уже кто-то ползет – отходит и каждый из тех кому нужно пролезть делает тоже самое.

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

Как пользоваться критическими секциями?

Для начала нужно объявить критическую секцию, добавив в uses syncobjs.

Объявить её можно где угодно – в зависимости от задачи. Часто её объявляют в глобальных переменных.

Далее, её нужно создать. Например в FormCreate

Далее, собственно само использование секции

Это в самом простом случае, но лучше пользоваться секциями try finally, чтобы гарантированно выйти из неё.

Тут повышается вероятность того, что мы выйдем корректно из критической секции.

И наконец последний шаг – уничтожение критической секции

В принципе это вся техника. Теперь один нюанс.

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


Практика

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

13

Для начала определим классы потоков и добавим syncobjs в uses

В глобальные переменные добавим

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

Теперь обработаем процедуры execute потоков

Теперь займемся созданием и уничтожением критических секций.

В FormCreate пропишем

В FormDestroy

Ну и последние шаги – собственно вызов потоков

Последний штрих – попробуем вызвать глобальную переменную GlobalVar.

Результат будет примерно таким как на рисунках ниже, при нажатии на Start 1 Thread. При этом, если нажать на Start 2 Thread, то второй поток не запустится пока не отработает первый. Также, если нажать на ShowGlobalVar, то можно считать глобальную переменную. Также можно написать процедуру, которая переписывает эту переменную и тогда счетчик будет сбиваться. Это хороший пример того что может произойти, если потоки получают доступ к одним и тем же ресурсам. Доступ к GlobalVar в данном случае осуществляется только потому, что переменная GobalVar находится вне критической секции.

13

14

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

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

This entry was posted in Delphi, Начальный уровень, Потоки(Threads). Bookmark the permalink.