Delphi. Потоки (TThread). Создание и синхронизация.

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

Что такое потоки? Зачем нужны потоки и как это работает?

Понятие потоков в Delphi перекочевало из Windows. Очень хорошо об этом написано в книгах Рихтера и Калверта. Но если своими словами и по простому – поток это участок кода, который выполняется параллельно.

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

В Windows работает механизм процессов и потоков. Процесс, согласно Чарльзу Калверту, подобен булеву выражению, он либо есть, либо нет. Потоки же выполняют всю основную работу.

Согласно Чарльзу Калверту это подобно игровому колесу в казино. Допустим оно поделено на 360 частей-секторов. Эти части и есть, образно, потоки. В каждый момент времени какая-то из частей пролетает под стрелкой, которая и укажет в конце выиграли вы или нет. Но выигрыш тут ни при чем. Нас интересует сам механизм. Аналогия в том, что в каждый момент времени одному из потоков выдается квант времени на то, чтобы он выполнил часть задачи. И все это происходит настолько быстро, что человек не успевает замечать. Вот так это работает. 


Как создать поток (TThread)?

Теперь посмотрим как это работает в Delphi. Для простоты будем работать с обычными VCL приложениями.

Для начала объявим класс потока в секции type

Обратите внимание на процедуру execute. Это и есть та процедура, в которой выполняется основной код потока.

Далее нам нужно прописать реализацию процедуры execute нашего потока.

Ну и последний шаг – создать экземпляр класса потока и где-то его запустить. В самом простом варианте это будет выглядеть примерно так. Код ниже можно поместить в обработчик кнопки. По нажатию поток запустится.

Но это в самом простом варианте, как правило потоки мало мальски настраивают. Например так.


Синхронизация потоков. Зачем и как?

Здесь тоже все относительно просто. Есть общий ресурс – компонент, переменная, что угодно, с чем работает один из потоков. И как так организовать, чтобы их действия не перемешивались как в том примере с колесом рулетки, а они могли работать последовательно? Скажем сначала отработал один поток, потом отработал другой поток и так далее. Для этого и существует синхронизация. Процедура, которая синхронизирует так и называется synchronize. В документации существует аж 4 перегруженных варианта этой процедуры

Пример синхронизации потоков

Создадим простейший пример. Создадим приложение VCL. На нём разместим TMemo и кнопку TButton.

24

Создадим сначала 2 потока 1 класса – поэкспериментируем с synchronize процедурой. А потом создадим ещё один поток другого класса и поэкспериментируем с функцией WaitFor

Итак, для начала создадим класс потока для первых двух потоков. В секции определения типов напишем

Далее напишем обработчики. Для Execute

Для addstr

На кнопке разместим следующий код

Тестируем режим без синхронизации

Для этого в коде поправим следующее

6

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

Тестируем в режиме синхронизации

Для этого в коде поправим следующее

5

Теперь, как видно из рисунка выше – потоки пользовались компонентом TMemo по очереди. Очередность можно настраивать через приоритеты (свойство Priority). Что касается видов приоритетов, то можно назначать следующие

Об этом можно узнать, если заглянуть в System.Classes и поиском пройтись скажем по ключевому слову tplower. Вот что поиск выдал у меня

Почему об этом говорю – во встроенной справке Delphi XE8 не нашел этого, поэтому полез в System.Classes.


Метод потока WaitFor

Нигде толкового описания не нашел, поэтому пришлось собирать все по кусочками и проводить мини-эксперименты.

WaitFor это метод, который говорит одному потоку дождаться, пока не закончит выполнение другой поток. Но есть тонкости – если применять этот метод, то нужно


1. У того потока, которого мы будем дожидаться, нужно отключить свойство FirstThread.freeonterminate := true; Иначе функции WaitFor нечего будет проверять – поток уничтожится, и поток который ждет не сможет дождаться.

2. Второе следует из первого. Уничтожением придется заниматься самостоятельно.


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

Итак, в ButtonClick в нашей программе закомментируем у 1 и 2 потоков FirstThread.freeonterminate := true;

Добавим ещё один класс потоков в нашу программу. Этот класс и будет ждать пока не выполнятся экземпляры других классов.

Теперь пропишем методы

Execute

OneMoreAddStr

Теперь обработаем ButtonClick

Обратите внимание ! Функция ThirdThread обладает приоритетом tpHighest. Это означало бы в текущих условиях, что она была бы выполнена первой! Но мы попросили 3 поток ждать, посмотрим что будет в этом случае!!!

Результат работы WaitFor


7

А вот что было бы, если мы не пользовались функцией WaitFor. Закоментируем её!

Также, при желании, можно раскомментировать Freeonterminate у первого и второго потоков. Теперь посмотрим что будет дальше!

8

То есть потоки отработали в том приоритете, который мы и задали. Сначала 3-й, обладающий приоритетом tpHighest. Потом 2 и 1 в произвольном порядке, так как у них приоритет одинаковый.

Думаю в общих основные моменты понятны. Мы разобрали следующее

-Как создать, настроить и запустить потоки?

-Как синхронизировать потоки?

-Как использовать функцию WaitFor?

Продолжим экспериментировать в следующих постах!!!

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