🧵 Многопоточность в играх 🧵
Уровень материала: 🐓 #senior
Многопоточность и асинхронность дополняют друг друга, из-за чего многие часто их путают на собеседованиях, но всё же они являются разными и самостоятельными концепциями. При этом каждая из них имеет свои специфические нюансы в контексте геймдева. Начнём с многопоточности.
⚡ Кратко про многопоточность и асинхронность:
Многопоточность — это выполнение разных частей программы параллельно в отдельных потоках, которые могут быть распределёны между различными физическими или логическими ядрами процессора.
Асинхронность — это подход к организации кода, при котором выполнение операции может быть приостановлено и продолжено позже , без блокировки потока. Это абстракция, позволяющая работать «как будто параллельно».
❓ Можно ли использовать многопоточность:
Игровой Runtime работает в одном потоке (почему – объяснено в статье). Но использовать другие потоки не запрещено. Если говорить про Unity, то движок просто не даст работать с его API вне главного потока. Однако работа со всем остальным, что не касается Unity, доступна.
Можно распараллеливать какие-то вычисления со своими или стандартными типами из .net. Например, рассчитать позиции для юнитов, получить в результате массив с числами, а потом в главном потоке этот массив скормить в Transform'ы.
Сделать это можно, как используя стандартный в .net System.Threading.Thread или Parallel из System.Threading.Tasks, так и Unity Job System (Хабр) (для этого не обязательно использовать Entities Framework). Но это не всегда оправдано.
Стоит заметить, что наиболее легко распараллеливаются именно циклы (5 итераций — в один поток, 5 – в другой и т.д.). Но для этого итерации должны быть независимыми друг от друга.
⚠ Основные проблемы многопоточности:
- Управление потоками – это дополнительная нагрузка. Поэтому распараллеливание каких-то незначительных задач приведёт только к ухудшению общего перформанса за счёт появления дополнительной неокупаемой нагрузки.
- Нужно подготовить входные данные и обработать выходные. Эта проблема обостряется особо в играх, т.к. для запуска доп. потоков нужно полностью устранить работу с API движка. Никаких MonoBehaviour, GameObject и пр.
- У потоков могут быть общие данные. Доступ к общим данным нужно как-то организовывать и согласовывать. Иначе есть риск возникновения взаимных блокировок (deadlock), состояния гонки (race condition) и других неприятностей.
Для синхронизации существует множество стандартных инструментов: lock'и, мониторы, мьютексты, семафоры, барьеры, атомарные операции.
При этом из-за взаимного ожидания общая эффективность параллельного кода будет ниже, чем могла бы быть.
🔗 Доп. контент:
—————————————