Многопоточность с очень большим количеством потоков

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

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

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

Каков хороший подход к обработке потоков, чтобы каждый узел работал так, как будто он работает в своем собственном потоке?

Если это имеет значение, я строю симуляцию в Python 2.7, но я готов перейти на что-то другое, если это имеет смысл.


person Liron    schedule 30.09.2013    source источник
comment
Вам следует подумать о реализации какой-либо очереди (потоков) или, по крайней мере, диспетчера для управления информационным потоком. Однако трудно сказать, не зная, чего вы хотите достичь.   -  person tamasgal    schedule 30.09.2013
comment
Запуск 1600 потоков, конечно, приведет к плохой производительности на обычном компьютере. Возможно, вам придется подумать о высокопроизводительном компьютере или вы можете попытаться использовать свой графический процессор для большей вычислительной мощности. GPU хорош для многопоточности.   -  person justhalf    schedule 30.09.2013
comment
@justhalf Конечно, это будет плохо. Каждый поток активен только в течение нескольких миллисекунд в секунду и спит все остальное время, поэтому я думаю, что проблема не в ресурсах ЦП, а скорее либо в количестве ядер, либо просто в существовании такого количества потоков и переключениях контекста, которые они требовать. Вместо 1600 я бы даже сказал 10000. Я ищу хорошее решение для аппроксимации этих узлов, работающих параллельно, чтобы максимизировать количество узлов, которые я могу запустить. Я не думаю, что переход на GPU действительно поможет здесь.   -  person Liron    schedule 30.09.2013
comment
Что вы подразумеваете под аппроксимацией этих узлов?   -  person justhalf    schedule 30.09.2013
comment
Аппроксимация их полных параллельных характеристик. Работа на обычном процессоре не позволяет мне просто дать каждому узлу свой собственный поток и предположить, что все они будут работать рука об руку. Например, у меня может быть один (или несколько) управляющих потоков, которые запускают более мелкие потоки только тогда, когда у одного из узлов есть информация для отправки, но в большинстве случаев для каждого узла нет ни одного потока.   -  person Liron    schedule 30.09.2013
comment
Пожалуйста, прочитайте GlobalInterpreterLock.   -  person Lasse V. Karlsen    schedule 30.09.2013
comment
@LasseV.Karlsen - я предполагаю, что даже использование Jython, включающее несколько узлов на ЦП, не поможет мне, если количество потоков будет 10 КБ или 100 КБ. Вероятно, здесь потребуется другая архитектура (и/или язык реализации).   -  person Liron    schedule 30.09.2013


Ответы (4)


Во-первых, действительно ли вы используете обычные потоки Python по умолчанию, доступные в интерпретаторе Python 2.7 по умолчанию (CPython), и весь ваш код написан на Python? Если это так, вы, вероятно, на самом деле не используете несколько ядер ЦП из-за глобальной блокировки интерпретатора, которую имеет CPython (см. https://wiki.python.org/moin/GlobalInterpreterLock). Возможно, вы могли бы попробовать запустить свой код под Jython, просто чтобы проверить, будет ли производительность лучше.

Вероятно, вам следует переосмыслить архитектуру своего приложения и переключиться на ручное планирование событий вместо использования потоков или, возможно, попробовать использовать что-то вроде гринлетов (https://stackoverflow.com/a/15596277/1488821), но это, вероятно, будет означать менее точное время из-за отсутствия параллелизма.

person Ivan Voras    schedule 30.09.2013
comment
Я использую Python, который поставляется с Canopy (думаю). Я предполагаю, что это обычный CPython. - person Liron; 30.09.2013
comment
Судя по гуглю, так оно и есть. Но, как и другие, вашу проблему лучше решить, используя другую архитектуру приложения. - person Ivan Voras; 01.10.2013
comment
Я переключил архитектуру на использование gevents, при этом каждый узел работает на своем собственном гринлете. Для моего приложения это на самом деле работает очень хорошо, потому что мне не нужна реальная параллельная функциональность, и параллелизма достаточно. (По сути, гринлет Мастера просто устанавливает флаг, когда Мастер активно получает соединения, а Слейвы запрашивают это значение непосредственно на Мастере.) Спасибо за идею. - person Liron; 01.10.2013

Для меня 1600 потоков звучит много, но не чрезмерно, учитывая, что это симуляция. Если бы это было производственное приложение, оно, вероятно, не было бы пригодным для производства.

Стандартная машина не должна иметь проблем с обработкой 1600 потоков. Что касается ОС, эта статья может предоставить вам с некоторыми представлениями.

Когда дело доходит до вашего кода, скрипт Python — это не собственное приложение, а интерпретируемый скрипт, и поэтому для его выполнения потребуется больше ресурсов ЦП.

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

person Olof Forshell    schedule 30.09.2013
comment
С# тоже стоит посмотреть. Он правильно выполняет потоки (в отличие от Python) и не так шокирует тех, кто привык к удобству Python; C/C++ может оказаться слишком спартанским для удобства. - person bazza; 30.09.2013

Не используйте для этого потоки. Если вы придерживаетесь Python, позвольте узлам выполнять свои действия один за другим. Если производительность, которую вы получаете при этом, в порядке, вам не придется использовать C/C++. Если действия, которые выполняет каждый узел, просты, это может сработать. В любом случае, нет никакой причины использовать потоки в Python вообще. Потоки Python можно использовать в основном для блокировки ввода-вывода, чтобы не блокировать вашу программу, а не для использования нескольких ядер ЦП.

Если вы действительно хотите использовать параллельную обработку и писать свои узлы так, как будто они действительно разделены и обмениваются только сообщениями, вы можете использовать Erlang (http://www.erlang.org/). Это функциональный язык, очень хорошо подходящий для выполнения параллельных процессов и обмена сообщениями между ними. Процессы Erlang не сопоставляются с потоками ОС, и вы можете создать их тысячи. Однако Erlang — чисто функциональный язык и может показаться крайне странным, если вы никогда не использовали такие языки. И это также не очень быстро, поэтому, как и Python, он вряд ли будет обрабатывать 1600 действий каждые 5 мс, если только действия не будут довольно простыми.

Наконец, если вы не получаете желаемой производительности при использовании Python или Erlang, вы можете перейти на C или C++. Тем не менее, все еще не используйте 1600 потоков. На самом деле использование потоков для повышения производительности разумно только в том случае, если количество потоков не превышает значительно количество ядер ЦП. В этом случае вам может понадобиться шаблон реактора (с несколькими потоками реактора) (http://en.wikipedia.org/wiki/Reactor_pattern). В библиотеке boost.asio есть отличная реализация паттерна реактора. Это объясняется здесь: http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with-boostasio/

person Ellioh    schedule 30.09.2013
comment
Моя цель здесь не в том, чтобы увеличить пропускную способность с помощью нескольких потоков, а в том, чтобы имитировать взаимодействие между узлами. На каждом узле очень мало обработки. Я посмотрю на erlang и посмотрю, как он выглядит. - person Liron; 30.09.2013

Некоторые случайные мысли здесь:

Я неплохо справился с несколькими сотнями таких потоков в Java; это можно сделать с помощью правильного языка. (Но я не пробовал это в Python.)

На любом языке вы можете запустить код главного узла в одном потоке; просто сделайте так, чтобы он непрерывно зацикливался, запуская код для каждого мастера в каждом цикле. Однако таким образом вы потеряете преимущества нескольких ядер. С другой стороны, вы также избавитесь от проблем многопоточности. (Вы могли бы иметь, скажем, 4 таких потока, используя ядра, но возвращая головную боль многопоточности. Это также снизит накладные расходы на потоки, но тогда есть блокировка...)

Одна большая проблема, с которой я столкнулся, заключалась в том, что потоки блокировали друг друга. Разрешение 100 потокам вызывать один и тот же метод для одного и того же объекта в одно и то же время, не дожидаясь друг друга, требует некоторого размышления и даже исследования. Я обнаружил, что моя многопоточная программа поначалу часто использовала только 25% 4-ядерного процессора, даже при полной загрузке. Это может быть одной из причин, по которой вы бежите медленно.

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

Стоит иметь больше потоков, чем ядер. Когда у вас есть два потока, они могут блокировать друг друга (и будут, если они будут делиться какими-либо данными). Если у вас есть код для запуска, который не будет блокироваться, вы хотите запустить его в своем собственном потоке, чтобы он не ждал, пока код, который блокируется, разблокируется и завершится. Я обнаружил, что как только у меня появилось несколько потоков, они начали множиться как сумасшедшие — отсюда и моя программа с сотнями потоков. Даже когда 100 потоков блокируются в одном месте, несмотря на всю мою гениальность, есть множество других потоков, чтобы занять ядра!

person RalphChapin    schedule 30.09.2013