Глава №4.
Многопоточные приложения.
Сегодня я решил сесть за клавиатуру и написать наконец четвертую
главу. Надо же вас чем то взбодрить - а то на неделю выпал
из жизни и вы меня потеряли ? Но ничего, я снова – онлайн.
Как вы знаете, я меньше всего люблю заброшенные сайты. Этот
сайт, надеюсь, таким не станет и я доведу дело до конца. Поэтому
я здесь и поэтому я сейчас пишу эти строчки. Безусловно, получившийся
вариант этой главы пока что - черновой. Не буду растягивать
вступление и начну …
А речь у нас сегодня пойдет о многопоточных приложениях.
Для начала рассмотрим, что же вообще является приложением
и, что такое поток (нитка, тред).
Представим нашу программу в виде ассемблерного кода. В большинстве
случаев весь код выполняется последовательно, изредка прерываясь
переходами от функции к функции. И реагируя на поступающие
сообщения операционной системы (на которые это приложение
подписалось).
Сделать одновременное выполнение параллельно двух кусков
кода невозможно физически (если имеется только один процессор),
однако можно сделать псевдопараллельное выполнение – т.е выполняются
по очереди команды то из одного куска, то из другого.
Таким образом, разделяют время ЦП (центрального процессорора)
приложения в системе Windows (в ДОСе не было механизмов, для
одновременного выполнения двух приложений, несмотря на это
существовали резидентные программы которые постоянно сидели
в памяти).
Windows с помощью встроенных средств очень быстро переключает
приложения и нам кажется, что они выполняются одновременно(хотя
на самом то деле это не так).
Как же быть, если требуется одновременное выполнение нескольких
кусков кода в одном приложении? Нам надо воспользоваться потоками.
Можно работать с потоками функциями WIN32, но это не очень
удобно, тем более что в C++ Builder есть встроенный класс
TThread.
Для чего все это может пригодиться в игре? Например, для
одновременного движения всех героев. В tl-survival (я написал
эту прогу еще до того, как узнал про треды) вы можете увидеть,
что, когда движется главный герой, все остальные застывают
на месте, а это, согласитесь, выглядит не очень то красиво.
Чтобы исправить это, необходимо создать нитку для каждого
героя (нужно запихать его в класс героя).
Рассмотрим это чуть позже, а сейчас вы узнаете, как в принципе
сделать тред в вашей программе. В свое время я очень долго
пытался сделать тред средствами WinAPI и лишь случайно обнаружил,
что в Билдере это можно сделать гораздо проще (не сложнее
чем создать новую форму).
Создать тред можно так: зайдите в File->New->Other
и в появившемся окошке выберите Thread Object.
У вас попросят ввести его имя. Введите любое осмысленное
название, к примеру, MyThread.
Теперь в вашем приложении появится новый Юнит с описанием
класса Треда.
Его основная функция Execute. в этой самой Экзекуции должен
быть прописан основной код процесса. Чтобы использовать поток
в первом Юните не забудьте подключить его туда. И, в свою
очередь, если вы надумаете использовать данные или объекты
из первого Юнита не забудьте его включить во второй.
Смысл подключения заголовочных файлов состоит в том, что переменные
и объекты из одного файла-Юнита вы сможете использовать в
другом.
Стоит обратить внимание, что подключение 1-го Юнита ко второму
не значит автоматического подключения 2-го к 1-му.
Подключать Юниты можно двумя способами.
1) В начале файла Unit1.cpp можно прописать #include "Unit2.h"
(после строчки #include "Unit1.h").
2) Более простой и быстрый - Зайти в меню File -> Include
Unit Hdr. Другое дело, что не рекомендуется пользоваться вторым
способом, не зная и не понимая первого.
После подключения взаимного подключения двух юнитов в экзекуте
вы можете написать такой код:
void __fastcall MyThread::Execute()
{
int i=0;
while (!Terminated)
{
i++;
Form1->Edit1->Text=i;
Sleep(50);
}
}
//---------------------------------------------------------------------------
Кстати, пример работы с Тредом
можно скачать здесь >>
Перейдем к тредам в играх. Мы остановились с вами в прошлый
раз на выделении героев мышкой. Теперь нам нужно объединить
все знания, полученные нами из предыдущих глав и написать
заново класс чувака. Вот приблизительно, что должно получиться:
class Chuvak
{
public : // обратить внимание !!! часто забывают прописать
public
Chuvak(); // конструктор
~Chuvak(); // деструктор
bool selected; // выделен или нет
int type; // тип
int x1,y1; // координаты начала движения
int x2,y2; // координаты конца движения
int live; // жизни (здоровье или очки или что-то в этом роде)
int boolean_path[20][20];
MyThread *SecondThread; // поток ходьбы
void run_thread(void); // запуск потока
bool create_thread(void); // создание потока
void calc_path(int a[20][20],int h[20][20]); // расчет кратчайшего
пути
void draw(TCanvas *Canvas); // отрисовка
};
Скачать готовый
пример №12
Дам некоторые комментарии: чтобы персонажи не перемещались
по кратчайшему пути мгновенно создается поток SecondThread.
В котором движения (шаги) персонажи совершают каждые 50 мс.
Почему такое название потока? Такое название по не помню уже
какой причине было в примере и мне хочется его оставить так
как было, чтобы вы не запутались … хотя, я чувствую,. вы и
так запутаетесь ? (кажется первый поток FirstThread – был
потоком атаки).
boolean_path – это двоичная карта, на которой двадцатками
среди нулей изображен путь, по которому надо идти.
Когда герой идет, он стирает за собой двадцатки и оставляет
нули. Следовательно, назад он уже не пойдет.
Вот демонстрация этого примера:
if(Form1->chuvaki[i].boolean_path[x1+1][y1]==20 &&
Form1->h[x1+1][y1]==0)
{
Form1->chuvaki[i].boolean_path[x1][y1]=0;
x1++;
}
if(Form1->chuvaki[i].boolean_path[x1-1][y1]==20 &&
Form1->h[x1-1][y1]==0)
{
Form1->chuvaki[i].boolean_path[x1][y1]=0;
x1--;
}
if(Form1->chuvaki[i].boolean_path[x1][y1+1]==20 &&
Form1->h[x1][y1+1]==0)
{
Form1->chuvaki[i].boolean_path[x1][y1]=0;
y1++;
}
if(Form1->chuvaki[i].boolean_path[x1][y1-1]==20 &&
Form1->h[x1][y1-1]==0)
{
Form1->chuvaki[i].boolean_path[x1][y1]=0;
y1--;
}
Далее - в конструкторе необходимо создавать поток, а в деструкторе
его убивать. Кстати, в конструкторе также прописываются значения
свойств по умолчанию.
Chuvak::Chuvak() // конструктор чувака (вызывается, когда
мы создаем его)
{
team=1; // по умолчанию - свой.
live=100;
SecondThread = new MyThread(true);
}
Chuvak::~Chuvak() // деструктор чувака (вызывается, когда
мы удаляем его)
{
SecondThread->Terminate(); /// завершаем поток
delete SecondThread; /// удаляем поток
}
В общем, смотрите на пример и делайте как там ...
Спрашивайте что здесь непонятно, я отвечу вам на любые вопросы
и, если потребуется, допишу ваши примечания в главу.
С уважением, ваш ToshibaNT
!