где в самом действии образуется конструкция вида
Понять, для каких данных нужно выполнять действие
Выполнить действие для этих данных
С практической точки зрения это означает, что теперь не только снаряды летают одновременно с перемещением танков, но и танки ездят не «по очереди», а все сразу. И поскольку танков и снарядов у нас довольно много, то, казалось бы, игра не просто параллелится, - она разбивается на сотни потоков и, стало быть, сможет получить дополнительные преимущества даже на будущих двухпроцессорных 32-ядерных системах с поддержкой четырехпоточного HyperThreading. Однако этого не происходит, и вот почему: запуск потока - весьма и весьма дорогая по меркам процессора процедура, которая требует немалого времени, грозящего свести на нет все преимущества параллельной обработки. Переключение между потоками - тоже процесс небыстрый, и если мы разбили исполнение программы на 32 потока, а процессор умеет исполнять только два потока одновременно, то постоянные переключения между шестнадцатью потоками на каждое виртуальное ядро очень сильно «просадят» производительность. А потому программисты зачастую отказываются от «простого» решения и прибегают к более сложной конструкции, когда все необходимые рабочие потоки (причем их число тщательно выбирается, чтобы исключить лишние переключения) запускаются заблаговременно, а в нужных местах «главный» поток «раздает» им текущие задания. Что-то вроде
ЗапуститьПоток(Поток1)
ЗапуститьПоток(Поток2)
ПопроситьПотокСделать(Поток1, Действие, для Объекта1)
ПопроситьПотокСделать(Поток2, Действие, для Объекта2)
В результате программист уже на начальном этапе вынужден возиться с довольно громоздкими и сложными конструкциями, которые далеко не так просто написать и отладить. И даже на этой первой, самой простой проблеме параллельного программирования многие спотыкаются. Чтобы облегчить жизнь новичкам и облегчить знакомство с параллельным кодом, существуют проекты типа OpenMP.