Как я могу убедиться, что N потоков работает примерно с одинаковой скоростью?

Я играю с идеей написания программного обеспечения для моделирования физики, в котором каждый физический элемент будет моделироваться в своем собственном потоке.

Было бы несколько преимуществ для этого подхода. Это было бы концептуально очень близко к тому, как работает настоящий мир. Было бы намного проще масштабировать систему на несколько машин.

Однако для этого мне нужно убедиться, что все потоки работают с одинаковой скоростью, с довольно либеральной интерпретацией "того же". Скажите в пределах 1% друг от друга.

Вот почему мне не обязательно нужно решение Thread.join(). Я не хочу, чтобы какая-то uber-управляющая школьная любовница обеспечивала постоянную синхронизацию всех потоков друг с другом. Я просто должен быть в состоянии спросить время выполнения (в зависимости от того, что это такое --- может быть Java, Erlang или что-то более подходящее для этой проблемы) для запуска потоков с более или менее равной скоростью.

Любые предложения будут чрезвычайно оценены.

ОБНОВЛЕНИЕ 2009-03-16

Я хотел поблагодарить всех, кто ответил на этот вопрос, в частности всех тех, чей ответ был по существу "НЕ ДЕЛАЙТЕ ЭТО". Теперь я понимаю, что моя проблема намного лучше благодаря всем комментариям, и я менее уверен, что должен продолжить, как я изначально планировал. Тем не менее я чувствовал, что ответ Питера был лучшим ответом на этот вопрос, поэтому я принял его.

Ответы

Ответ 1

Вам понадобится какая-то синхронизация. CyclicBarrier класс имеет то, что вам нужно:

Помощь по синхронизации, которая позволяет множество нитей для всех ждут каждого другой - для достижения общей барьерной точки. CyclicBarriers полезны в программах с участием фиксированной партии потоки, которые должны время от времени ждать друг для друга. Барьер называется циклический, поскольку он может быть повторно использован после ожидающие потоки освобождаются.

После каждого "тика" вы можете позволить всем вашим потокам ждать других, которые были медленнее. Когда оставшиеся нити достигнут барьера, все они продолжатся.

Ответ 2

Вы не можете сделать это без согласования. Что делать, если один элемент в итоге нуждался в более дешевых вычислениях, чем другой (потенциально неочевидным образом)?

Вам не обязательно нужен uber-контроллер - вы можете просто сохранить какой-то счетчик шагов для каждого потока и иметь глобальный счетчик, указывающий "самый медленный" поток. (Когда каждый поток проделал какую-то работу, ему пришлось бы проверить, упал ли он за другими, и обновите счетчик, если это так.) Если поток отмечает, что он намного опережает самый медленный поток, он может просто ждать коротко ( потенциально на мониторе).

Просто делайте это каждый раз, чтобы избежать слишком больших издержек из-за совместного использования данных, и я думаю, что он может работать достаточно хорошо.

Ответ 3

Потоки предназначены для автономной работы независимо друг от друга, что означает, что синхронизация их каким-либо образом всегда является болью. В вашем случае вам нужны центральные "часы", потому что нет способа сообщить VM, что каждый поток должен получать одинаковое количество... мм... что он должен получить? Такой же объем оперативной памяти? Наверное, не имеет значения. Такой же объем процессора? Все ли ваши объекты настолько похожи, что для каждого из них требуется одинаковое количество инструкций ассемблера?

Итак, мое предложение - использовать центральные часы, которые транслируют тики часов для каждого процесса. Все потоки в каждом процессе читают тики (которые должны быть абсолютными), вычисляют разницу с последним тиком, который они видели, и затем обновляют свою внутреннюю модель соответственно.

Когда поток обновляется, он должен спать; ожидая следующего тика. В Java используйте wait() в блокировке "tick received" и просыпайте все потоки с помощью "notifyAll()".

Ответ 4

Я бы рекомендовал не использовать потоки везде, где это возможно, потому что они просто добавляют проблемы позже, если вы не будете осторожны. При физическом моделировании вы можете использовать сотни тысяч дискретных объектов для более крупных симуляций. Вы не можете создать много потоков на любой ОС, о которой я знаю, и даже если бы вы могли это сделать, как дерьмо!

В вашем случае вы можете создать несколько потоков и поместить цикл событий в каждый поток. Поток "master" мог бы выполнить выполнение и отправить событие "process" для каждого рабочего потока, чтобы разбудить его и заставить его выполнить некоторую работу. Таким образом, потоки будут спать, пока вы не скажете им, чтобы они работали.

Вы должны иметь возможность заставить основной поток галочкой со скоростью, которая позволяет всем вашим рабочим потокам завершить до следующего тика.

Я не думаю, что потоки являются ответом на вашу проблему, за исключением параллелизма на небольшое количество рабочих потоков (равное количеству ядер в машине), которые каждая линейно упорядочивает ряд физических объектов. Таким образом, вы все равно можете использовать подход, основанный на главном/управляемом событиями, но вы бы устранили много накладных расходов.

Ответ 5

Пожалуйста, не надо. Нити - это абстракция O/S, позволяющая возникновение параллельного выполнения. С несколькими и многоядерными процессорами O/S может (но не обязательно) распределять потоки между различными ядрами.

Ближе всего к вашему видению масштабируемости, который я считаю работоспособным, является использование рабочих потоков, размер которых примерно соответствует количеству ядер, которые у вас есть, и распределять между ними работу. Грубый черновик: определите класс ActionTick, который выполняет обновление для одной частицы, и пусть рабочий поток выбирает ActionTicks для обработки из общей очереди. Я вижу несколько проблем даже при таком решении.

  • Накладные расходы на потоки: вы получаете перераспределения контекста между различными рабочими потоками. Потоки сами по себе являются дорогостоящими (если не такими же разрушительными, как процессы): производительность теста с различными размерами пула потоков. Добавление большего количества потоков за пределы количества ядер приводит к снижению производительности!
  • Расходы на синхронизацию: вы получаете несколько точек состязания: доступ к рабочей очереди для одного, но хуже, доступ к имитируемому миру. Вам нужно разграничить эффекты каждого ActionTick или реализовать много блокировки/разблокировки.
  • Сложность оптимизации физики. Вы хотите разграничить количество объектов/частиц, на которые каждый ActionTick смотрит (расстояние отрезка? 3D-дерево-подразделение пространства моделирования?). В зависимости от домена моделирования вы можете устранить много работы, изучив, нужны ли какие-либо изменения в подмножестве элементов. Выполнение таких оптимизаций проще перед работой над очередями, а не как распределенный алгоритм. Но тогда эта часть вашей симуляции станет потенциальным узким местом масштабируемости.
  • Сложность. Threading и concurrency представляют несколько решений червей для решения. Всегда сначала учитывайте другие варианты, но если они вам понадобятся, попробуйте выполнить потоки, прежде чем создавать собственные стратегии планирования, блокировки и выполнения рабочих элементов...

Предостережение: я не работал с каким-либо мощным симуляционным программным обеспечением, а всего лишь с некоторым кодом для хобби.

Ответ 6

Как вы упомянули, есть много ответов "НЕ ДЕЛАЙТЕ ЭТО". Большинство, похоже, читают потоки как потоки ОС, используемые Java. Поскольку вы упоминали Эрланг в своем посте, я бы хотел написать более эрланговый ответ.

Моделирование такого моделирования с помощью процессов (или актеров, микропоток, зеленых потоков, как их иногда называют) не обязательно требует синхронизации. По сути, у нас есть пара (по всей вероятности, тысячи или сотни тысяч) объектов физики, которые нужно моделировать. Мы хотим, чтобы имитировать эти объекты как можно реалистичнее, но, вероятно, есть и какой-то аспект реального времени (не обязательно, хотя вы не упомянули об этом в своем вопросе).

Простым решением было бы создать процесс Erlang для каждого объекта, послать отметки всем им и собрать результаты моделирования, прежде чем продолжить следующий тик. Это на практике синхронизирует все. Это, конечно, более детерминированное решение и не гарантирует никаких свойств в реальном времени. Также нетривиально, как процессы будут разговаривать друг с другом, чтобы получить нужные им данные для расчетов. Вероятно, вам нужно сгруппировать их умными способами (группы конфликтов и т.д.), Иметь спящие процессы (которые Erlang имеет аккуратную поддержку) для спящих объектов и т.д., Чтобы ускорить процесс.

Чтобы получить свойства в реальном времени, вам, вероятно, необходимо сдерживать вычисления, выполняемые процессами (точность торговли для скорости). Возможно, это может быть сделано путем отправки тиков, не дожидаясь ответов, и позволяя объектным процессам отвечать на каждый тик с их текущей позицией и другими данными, которые вам нужны (хотя это может быть только приблизительным в то время). Как говорится в DJClayworth, это может привести к накоплению ошибок при моделировании.

Я думаю, в каком-то смысле вопрос в том, действительно ли можно использовать силу concurrency, чтобы получить какое-то преимущество здесь. Если вам нужна синхронизация, это довольно сильный знак, что вам не нужно concurrency между каждым физическим объектом. Потому что вы существенно выбрасываете много времени для вычисления, ожидая других процессов. Вы можете использовать concurrency во время вычисления, но это другое обсуждение, я думаю.

Примечание. ни одна из этих идей не учитывает фактические вычисления физики. Это не сильная сторона Erlang и, возможно, может быть выполнена в библиотеке C или на любой другой странице, в зависимости от типа характеристик, которые вы хотите.

Примечание: Я не знаю ни одного случая, когда это было сделано (особенно не мной), поэтому я не могу гарантировать, что это разумный совет.

Ответ 7

Даже при отличном программном обеспечении аппаратное обеспечение предотвратит это. Аппаратные потоки, как правило, не имеют хорошей производительности. За короткий период вам повезет, если потоки будут работать с производительностью +10%.

Это, конечно, выбросы. Некоторые чипсеты будут запускать некоторые ядра в режиме энергосбережения, а другие нет. Я полагаю, что одна из исследовательских машин Blue Gene имела программное управление планированием аппаратных потоков вместо блокировок.

Ответ 8

Erlang по умолчанию попытается распределить свои процессы равномерно по доступным потокам. Он также по умолчанию попытается запустить потоки всех доступных процессоров. Поэтому, если у вас достаточно запущенных процессов Erlang, вы получите относительно ровный баланс.

Ответ 9

Я не эксперт по потокам, но не все точки потоков, которые они независимы друг от друга - и не детерминированные?

Ответ 10

Я думаю, что у вас есть фундаментальное заблуждение в вашем вопросе, где вы говорите:

Было бы концептуально очень близко к тому, как работает настоящий мир

Реальный мир не работает нитьным образом. Нити в большинстве машин не являются независимыми, а не фактически даже одновременными (вместо этого OS будет использовать контекстное переключение). Они обеспечивают наибольшую ценность, когда есть много IO или ожидания.

Самое главное, что реальный мир не "потребляет больше ресурсов", поскольку происходят более сложные вещи. Подумайте о различии между двумя предметами, падающими с высоты, один падающий плавно, а другой - каким-то сложным поворотным движением...

Ответ 11

Я бы сделал своего рода "генератор часов" - и зарегистрировал бы каждый новый объект/поток там. Часы будут уведомлять все зарегистрированные объекты, когда дельта-t прошла. Однако это не означает, что вам нужен отдельный поток для каждого объекта. В идеале у вас будет столько потоков, сколько процессоров. С точки зрения дизайна вы можете отделить выполнение объектных задач через Исполнителя или пул потоков, например. когда объект получает событие tick, он переходит в пул потоков и сам планирует выполнение.

Ответ 12

Для достижения этой цели должны произойти две вещи. Вы должны убедиться, что у вас одинаковое количество потоков на ядро ​​процессора, и вам нужна какая-то синхронизация.

Эта синхронизация может быть довольно простой, например, проверка переменной цикла для каждого потока при выполнении вычислений, но вы не можете избежать этого.

Ответ 13

Работая при управлении двигателями, я использовал некоторую математику для поддержания скорости в стабильном состоянии. Система имеет ПИД-регулирование, пропорциональное, интегральное и производное. Но это аналоговая/цифровая система. Может быть, можно использовать аналогично, чтобы определить, как время запуска каждого потока должно выполняться, но самый большой совет, который я могу вам дать, - это то, что все потоки будут иметь синхронизацию часов.

Ответ 14

Прежде всего я признаю, что я не эксперт по потокам, но это похоже на неправильный подход к моделированию. Как уже отмечали другие, слишком много потоков является дорогостоящим. Кроме того, если вы планируете делать то, что, как я думаю, вы собираетесь делать, ваша симуляция может оказаться случайной (не имеет значения, если вы делаете игру).

Я бы пошел с несколькими рабочими потоками, используемыми для вычисления дискретных шагов моделирования.