Як я можу зробити повідомлення, що проходять між потоками в багатопотоковому двигуні, менш громіздкими?


18

Двигун C ++, над яким я зараз працюю, розбивається на кілька великих потоків - Покоління (для створення мого процедурного вмісту), Ігровий процес (для AI, сценаріїв, моделювання), Фізика та Візуалізація.

Нитки спілкуються між собою через невеликі об’єкти повідомлень, які переходять від потоку до потоку. Перед кроком нитка обробляє всі свої вхідні повідомлення - оновлення для перетворень, додавання та видалення об'єктів тощо. Іноді один потік (Генерація) щось створить (Art) та передасть його іншому потоку (Візуалізація) для постійного володіння.

На початку процесу я помітив кілька речей:

  1. Система обміну повідомленнями громіздка. Створення нового типу повідомлення означає підкласинг базового класу повідомлень, створення нового перерахунку для його типу та написання логіки того, як потоки повинні інтерпретувати новий тип повідомлення. Це швидкість для розвитку та схильна до помилок у стилі друку. (Sidenote - працюючи над цим, мене оцінює, наскільки великими можуть бути динамічні мови!)

    Чи є кращий спосіб зробити це? Чи варто використовувати щось на зразок boost :: bind, щоб зробити це автоматичним? Я переживаю, що якщо я це зроблю, я втрачаю здатність говорити, сортувати повідомлення на основі типу чи чогось іншого. Не впевнений, чи стане таке управління навіть необхідним.

  2. Перший момент важливий, оскільки ці нитки так сильно спілкуються. Створення та передача повідомлень є важливою частиною того, щоб все сталося. Я хотів би упорядкувати цю систему, але також бути відкритим для інших парадигм, які можуть бути так само корисними. Чи є різні багатопотокові конструкції, над якими я повинен подумати, щоб полегшити це?

    Наприклад, є деякі ресурси, які нечасто написані, але часто читаються з декількох потоків. Чи повинен я бути відкритим до ідеї мати спільні дані, захищені мутексами, щоб усі потоки мали доступ?

Це мій перший раз, коли я розробляю щось із багатопоточним розумінням з самого початку. На цій ранній стадії я насправді думаю, що це йде дуже добре (враховуючи), але я переживаю за масштабування та власну ефективність щодо впровадження нових матеріалів.


6
Тут насправді немає жодного спрямованого питання, і як такий, ця публікація не дуже підходить для стилю запитань на цьому веб-сайті. Я рекомендую вам розділити свою публікацію на окремі повідомлення, по одній на запитання, і переорієнтувати питання, щоб вони задавали певну проблему, яку ви насправді маєте, а не розпливчасту колекцію порад чи порад.

2
Якщо ви хочете взяти участь у більш загальній розмові, рекомендую спробувати це повідомлення на форумах на сайті gamedev.net . Як сказав Джош, оскільки ваше "питання" не є жодним конкретним питанням, було б досить складно розміститись у форматі StackExchange.
Cypher

Дякую за відгуки хлопці! Я якось сподівався, що хтось із більшими знаннями може мати єдиний ресурс / досвід / парадигму, який може вирішити декілька моїх проблем одночасно. У мене виникає відчуття, що одна велика ідея могла б синтезувати мої різні проблеми в одне, чого мені не вистачає, і я думав, що хтось із більшим досвідом, ніж я, міг би це визнати ... Але, можливо, ні, і так чи інакше - бали !
Raptormeat

Я перейменував ваш заголовок, щоб бути більш конкретним щодо передачі повідомлень, оскільки питання типу "підказки" означають, що не існує конкретної проблеми для вирішення (і тому в наші дні я закриваюсь як "не справжнє питання").
Тетрад

Ви впевнені, що вам потрібні окремі теми для фізики та гри? Ці два, здається, дуже переплітаються. Крім того, важко знати, як запропонувати поради, не знаючи, як кожен з них спілкується і з ким.
Нікол Болас

Відповіді:


10

До вашої більш широкої проблеми подумайте, як спробувати знайти способи максимально скоротити міжпотокове спілкування. Краще взагалі уникати проблем синхронізації, якщо можете. Цього можна досягти подвійним буферизацією ваших даних, введенням затримки однократного оновлення, але значно полегшує завдання роботи із спільними даними.

Як осторонь, чи розглядали ви не нарізання ниток по підсистемі, а замість цього використовуєте нерестовий поріз потоку чи пул потоків, щоб розщедритися за завданням? (див. це стосовно вашого конкретного випуску щодо об'єднання потоків.) Цей короткий документ коротко описує мету та використання схеми пулу. Дивіться ці інформативні відповідітакож. Як зазначалося там, нитки пулів покращують масштабованість як бонус. І це "пишіть один раз, використовуйте будь-де", на відміну від необхідності отримувати потоки на основі підсистеми, щоб грати добре, коли ви пишете нову гру чи движок. Тут також існує безліч солідних рішень для об'єднання потоків потоків. Було б простіше почати з нерестування ниток і відпрацювати свій шлях до нитки басейнів пізніше, якщо витрати на нерест і знищення ниток потрібно пом'якшити.


1
Будь-які рекомендації щодо конкретних ліній для об'єднання ниток, щоб перевірити?
imre

Нік - велике спасибі за відгук. Щодо Вашого першого пункту - я думаю, що це відмінна ідея, і, мабуть, напрямок, в який я рухатимусь. На даний момент досить рано, що я ще не знаю, що потрібно було б отримати з двома буферами. Я маю це на увазі, оскільки це з часом твердне. До вашого другого пункту - дякую за пропозицію! Так, переваги ниткових завдань зрозумілі. Я прочитаю ваші посилання та подумаю. Не на 100% впевнений, чи буде це працювати для мене / як змусити це працювати для мене, але я обов'язково задумаюся про це серйозно. Спасибі!
Raptormeat

1
@imre перевірити бібліотеку Boost - у них є ф'ючерси, які є приємним / простим способом наближення до цих речей.
Джонатан Дікінсон

5

Ви запитували про різні багатопотокові конструкції. Мій друг розповів мені про цей метод, який я вважав досить крутим.

Ідея полягає у тому, щоб було 2 примірники кожної ігрової сутності (я знаю марно). Один примірник був би теперішньою копією, а другий - минулою. Нинішня копія суворо лише для запису , а попередня копія - лише для читання . Коли ви переходите до оновлення, ви присвоюєте діапазони списку ваших організацій стільки потоків, скільки вважаєте за потрібне. Кожен потік має доступ до запису до теперішніх копій у присвоєному діапазоні, і кожен потік має доступ до читання до всіх попередніх копій об'єктів, і таким чином може оновлювати призначені присутні копії, використовуючи дані з минулих копій без блокування. Між кожним кадром теперішня копія стає минулою копією, проте ви хочете обробити заміну ролей.


4

У нас була та сама проблема, тільки із C #. Після довгого і серйозного роздумів про легкість (або її відсутність) створення нових повідомлень найкраще, що ми могли зробити, - це зробити для них генератор коду. Це трохи некрасиво, але зручно: якщо давати лише опис вмісту повідомлення, він генерує клас повідомлень, перерахунки, код обробки заповнювачів заповнення тощо тощо - весь цей код, який майже однаковий кожен раз, і справді схильний до друку.

Я не зовсім задоволений цим, але краще, ніж писати весь цей код вручну.

Щодо спільних даних, найкраща відповідь - це, звичайно, "це залежить". Але загалом, якщо деякі дані читаються часто і потрібні багатьма потоками, обмін цим варто. Для безпеки потоку найкраще зробити це непорушним , але якщо це не виникає сумніву, мьютекс може зробити. У C # є ReaderWriterLockSlimклас, спеціально призначений для таких випадків; Я впевнений, що є еквівалент С ++.

Ще одна ідея для потокового спілкування, яка, ймовірно, вирішує вашу першу проблему, - це передавати обробники замість повідомлень. Я не впевнений, як опрацювати це в C ++, але в C # ви можете відправити delegateоб'єкт в інший потік (як у, додати його до якоїсь черги повідомлень) і насправді викликати цього делегата з приймаючої нитки. Це дає можливість створювати "спеціальні" повідомлення на місці. Я грав лише з цією ідеєю, ніколи насправді не пробував її у виробництві, так що вона може виявитися поганою насправді.


Дякую за всю чудову інформацію! Останній біт про обробники схожий на те, що я згадував про використання прив'язки чи функторів для передачі функцій. Мені якось подобається ідея - я можу спробувати її і побачити, чи вона смокче або приголомшливо: D Можна почати із створення класу CallDelegateMessage і занурення мого пальця у воду.
Raptormeat

1

Я знаходжуся лише на стадії розробки якогось кодового ігрового коду, тому можу поділитися лише своїми думками, а не будь-яким фактичним досвідом. З урахуванням сказаного я думаю по наступних напрямках:

  • Більшість даних про ігри повинні бути спільними для доступу лише для читання .
  • Запис даних можливий, використовуючи вид обміну повідомленнями.
  • Щоб уникнути оновлення даних, коли читає їх інший потік, цикл гри має дві різні фази: читання та оновлення.
  • На етапі читання:
  • Усі спільні дані доступні лише для читання для всіх потоків.
  • Нитки можуть обчислювати матеріали (використовуючи локальне зберігання потоків) та створювати запити на оновлення , які в основному є об'єктами команд / повідомлень, розміщеними у черзі, які слід застосувати пізніше.
  • На етапі оновлення:
  • Усі спільні дані є лише для запису. Дані слід припускати у невідомому / нестабільному стані.
  • Тут обробляються об'єкти запиту на оновлення.

Я думаю (хоча я не впевнений) в теорії це повинно означати, що і на етапах читання та оновлення будь-яка кількість потоків може працювати одночасно з мінімальною синхронізацією. На етапі читання ніхто не записує спільні дані, тому проблем з одночасністю не повинно виникати. Етап оновлення, ну, це складніше. Паралельне оновлення однієї частини даних було б проблемою, тому тут потрібна деяка синхронізація. Однак я все ще можу запустити довільну кількість потоків оновлення, якщо вони працюють на різних наборах даних.

В цілому, я думаю, що цей підхід добре піддавався б системі об'єднання ниток. Проблемні частини:

  • Синхронізація потоків оновлення (переконайтеся, що жодна кількість потоків не намагається оновити один і той же набір даних).
  • Переконайтесь, що на етапі читання жодна тема не може випадково записати спільні дані. Я боюся, що буде занадто багато місця для помилок програмування, і я не впевнений, скільки з них можна було б легко зачепити інструментами налагодження.
  • Написання коду таким чином, що ви не можете розраховувати на те, що ваші проміжні результати будуть доступні для читання відразу. Тобто ви не можете писати, x += 2; if (x > 5) ...якщо спільний x. Вам потрібно або зробити локальну копію x, або надіслати запит на оновлення, а лише виконати умовне виконання в наступному запуску. Останнє означало б цілу масу додаткового кодово-котлового коду, що зберігає місцевий потік.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.