Для начала рассмотрим «традиционный» подход к программированию, который в нашем модельном примере выглядит следующим образом. Пишется некоторый кусочек кода (назовем его GameTick), который последовательно перебирает все имеющиеся в игре объекты, «вычисляя» события, происходящие с ними в данный момент времени (Tick, тик). Скажем, один объект - солдат в окопе - «поразмыслил» своим модулем AI, принял решение бросить гранату и бросил ее, сгенерировав новый игровой объект - летящую гранату. Другой объект - брошенный другим солдатом секунду назад «коктейль Молотова» - в результате вычислений физического движка изменил свое положение в пространстве, прошел проверку на соприкосновение с броней танка и прекратил существование. Танк, угодивший под бутылку с зажигательной смесью, перешел из состояния «танк обыкновенный» в состояние «танк горящий». Другой танк повернул башню еще на пять градусов влево. На этом игровые объекты, требующие вычислений, закончились, и сцена с игровыми объектами ушла на обработку к графическому движку, который конвертировал абстрактных солдат, танков и игровое поле во вполне осязаемые полигоны и текстуры, понятные видеокарте. При этом на экране появилась описанная выше картинка, сменившая предшествовавший кадр. А меж тем наша программа опросила клавиатуру и мышь (не решил ли пользователь как-то повлиять на ситуацию?) и перешла на уже известный нам участок кода делать очередной GameTick. Добавим сюда музыкальное сопровождение по вкусу - и игра «заработала»[Разумеется, все не так просто, но в первом приближении можно довольствоваться и этой моделью]. Танки ездят, снаряды летают, геймер отчаянно дает бойцам указания мышкой, в колонках «бумкает» все, что положено… остается лишь записать свежесотворенный шедевр на DVD и топать к издателю.
А теперь представим, что все то же самое мы хотим сделать «параллельно».
Зачем? Ну хотя бы затем, что сегодня это модно. И без технологии HyperThreading и оптимизаций под нее нам уже года три как не жить. На самом деле, конечно, причина грядущего перехода к параллельному программированию в том, что крупнейший производитель процессоров для ПК - корпорация Intel - обещает, что к концу следующего года более 70% продаваемых ею процессоров[А стало быть, как минимум половина всех продаваемых x86-совместимых процессоров] будут двухъядерными, - с чуть меньшей производительностью в пересчете на один-единственный поток исполнения, но зато выполняющие два (или даже четыре) потока одновременно[Подробнее о двухъядерных процессорах см. на offline.computerra.ru/2005/594/39218]. Поэтому если программист сумеет хорошо «раскидать» программу на два параллельных потока (сам процессор делать этого не умеет), то он получит на двухъядерном чипе гораздо большую производительность, чем на аналогичном по стоимости одноядерном. А если не сумеет - то получится как в хорошо знакомом всем россиянам изречении о том, что «хотели как лучше…». Таким образом, налицо и кнут (грозящее снижение производительности, если оставить все «как есть»), и пряник (потенциальный прорыв в скорости) - более чем убедительные аргументы за то, чтобы не отставать от технического прогресса.
Но как это вообще делается? В классическом варианте - полностью вручную. Главный поток программы (который создала при запуске приложения операционная система) формирует (посредством специальных системных вызовов) несколько новых потоков[В случае Unix-систем при этом происходит весьма нетривиальная вещь: при создании первого потока «главный» поток как бы «замораживается» операционной системой, а в операционной системе возникают еще две сущности - новый поток, запущенный по просьбе «главного», и «наследующий» поток, который продолжает исполнение «главного» кода, но не является собственно процессом], приступающих к выполнению программы с того места, которое указывается в числе параметров вызова. Детали реализации в разных ОС отличаются[Существует два основных стандарта: используемый в мире Open Source стандартный интерфейс pthreads (POSIX Threads) и детище Microsoft - так называемая Win32 Threading model], однако принцип совершенно одинаков: одна программа, одни и те же данные, несколько точек исполнения, одновременно перемещающихся по программе. Таким образом, вместо кода типа
Выполнить Действие1( )