Форум сайта python.su
Всем доброго времени!
Возникла задача реализовать некоторое подобие сканера портов по большому диапазону IP-адресов. Конечно, в один поток данная задача не решается, поэтому исследовал различные варианты многопоточности/асинхронности. Имею довольно большой опыт написания сетевых многопоточных приложений, однако с асинхронностью и зелеными потоками дела не имел. Задача сводится к попытке соединения на определнный порт каждого IP-адреса с заданным таймаутом (например, 1 секунда).
Насколько я понимаю, в питоне такую задачу можно решить двумя способами:
1. Создать пул нативных потоков (пробовал создавать 1000 штук) и в цикле обойти все IP.
2. Создать пул зеленых потоков (пробовал создавать 2000 штук, использовал eventlet) и в цикле обойти все IP.
Проблема решается и тем, и другим подходом, однако хочется наибольшую скорость при наименьших затратах ресурсов. Прошу просветить меня в следующих вопросах:
Вопрос №1: по-идее можно создать на каждый IP-адрес свой поток (зеленый или нет), но, насколько я понимаю, использование пула предпочтительнее, т. к. мы не тратим постоянно ресурсы на создание новых потоков + при большом количестве IP нативных потоков столько просто не создать вследствие ограниченности ресурсов. Правильно?
Вопрос №2: чем вообще зеленый поток отличается от обычного в контексте питона? Ну, то есть я понимаю, что threading создает поток ОС, а green thread - это просто событийная IO-модель (?), но по-сути, имея GIL, все операции будут выполняться одним ядром, то есть разницы в скорости зеленых потоков и обычных не будет? Или все дело в том, что зеленые потоки легче и быстрее переключается контекст? На самом деле все равно немного запутался тут
Вопрос №3: насколько вероятен вариант, что таймаут сокета в 1 сек при однопоточном приложении отличается от такового в многопоточном/асинхронном? Ну то есть не получится ли, грубо говоря, такой ситуации, что тысяча сокетов начали соединяться, к 0.99 сек все готовы получать данные, но пока очередь получения дошла до последнего, у него уже вывалился таймаут, хотя коннекшен был произведен за время < 1 сек. Ну и могут ли как-то еще потоки/зеленые потоки мешаться друг другу (не в смысле синхронизации, а именно в смысле недополучения данных и т.д.)?
Вопрос №4: какая модель наиболее хорошо способна решить данную задачу? Интересна стабильность работы, скорость, требовательность к ресурсам. Какую библиотеку стоит использовать из множества (gevent, eventlet, coroutines, etc.)? В чем разница между ними? Вопросы сложности написания кода под конкретную библиотеку не рассматриваются, задача сама-по-себе элементарная, так что тут проблем не будет.
Вопрос №5: вчера тестировал eventlet, вроде с ней все хорошо получается, однако не очень понял, почему на 2000 микропотоков и на 3000 прироста в скорости не ощущается? Трафик идет порядка 300 кб/сек, загрузка процессора порядка 30%, то есть вроде запасы есть большие, а не растет скорость почему-то.
Спасибо все большое за ответы, толковой информации мало где нашел по данной теме (именно по сравнению технологий). Везде видел только какие-то конкретные реализации конкретных программ, но вот так чтобы кто-то объяснил, чем технологии отличаются и что когда лучше использовать - не нашел. Прошу прощения за много текста
P. S.: насколько я понял, если такое приложение писать на Erlang, то все было бы супер-классно, но увы
Отредактировано skavans (Янв. 23, 2014 00:29:10)
Офлайн
1) Правильно.
skavansРаз у вас есть 2 рабочих кода- сравните их и по скорости, и по ресурсам. Отпишитесь о результатах.
Проблема решается и тем, и другим подходом, однако хочется наибольшую скорость при наименьших затратах ресурсов.
Офлайн
Завтра обязательно сравню и отпишусь, сегодня проблема со временем, поэтому решил для начала спросить совета
Помимо практических тестов, хочется досконально разобраться в данных технологиях, поэтому вопросы 2-5 все равно актуальны)
Отредактировано skavans (Янв. 23, 2014 01:28:23)
Офлайн
Время позднее, поэтому я коротко.
Потоки ограничены GIL.
В питоне есть модуль multiprocessing, который использует процессы (напоминаю об ограничении в ОС на кол-во процессов) и на которые GIL не влияет, может использовать многопроцессорность. Хотя вам она, вроде не нужна, т.к. узким местом в таких задачах обычно бывает сеть - I/O.
Если же использовать потоки, то я бы взял gevent. Помнится, она легко может забить работой 100-мигабитную сеть. Возможно, ее хватит.
В целом - создаете пул исполнителей, очередь IP, исполнители дергают из очереди IP, если произошла ошибка в процессе обработки, возвращают IP в очередь.
IP, которые не могут быть обработаны в принципе (вычислять по типу возникшего исключения) - в лог и убирать из очереди, чтобы не зацикливалось.
По вопросам.
1. Да, пул.
2. treading создает поток внутри процесса Питона, green thread - это то же самое.
3. забудьте, используйте пул gevent или multiprocessing, таймаут я бы все-таки увеличил, если у вас есть IP на другом конце земного шарика.
4. Мне нравится gevent, он быстрее eventlet, т.к. использует написанные на C библиотеки. С Корутином не работал, ничего не скажу.
5. Идет переключение контекста, скорее всего. Т.е. на обслуживание потоков в какой-то момент начинает тратиться больше времени, чем на выполнение работы в потоках.
Офлайн
Lexander, спасибо за ответ)
Да, и еще такой вопрос: Stackless Python со своими тасклетами способен дать реально более высокую скорость работы, чем тот же gevent? Или не стоит заморачиваться в данной задаче?
Офлайн
Для большого кол-ва открытых соединений лучше использовать зеленые потоки либо асинхронность (до мульти-процессов), везде пишут что обычные потоки хавают много памяти (но я бы не сказал, что они много памяти хавают - для теста создавал много потоков, замерял память).
Lexanderзеленые потоки не создают доп. потоков, они работают в одном потоке, переключение происходит сохранением стека и восстановление другого стека в том же потоке (“грязный хак”).
2. treading создает поток внутри процесса Питона, green thread - это то же самое.
skavansэто загрузка всего процессора? сколько нагрузка на одно ядро? какую нагрузку создает само приложение?
однако не очень понял, почему на 2000 микропотоков и на 3000 прироста в скорости не ощущается? Трафик идет порядка 300 кб/сек, загрузка процессора порядка 30%,
skavansна сколько я знаю, green thread - это попытка скопировать тасклеты из Stackless Python, т.е. то же самое, но в stackless они сделаны по нормальному - на “уровне ядра”.
Stackless Python со своими тасклетами способен дать реально более высокую скорость работы, чем тот же gevent?
Отредактировано o7412369815963 (Янв. 23, 2014 10:32:12)
Офлайн
o7412369815963Да, именно так. Я же написал “поток” - в единственном числе.
зеленые потоки не создают доп. потоков, они работают в одном потоке, переключение происходит сохранением стека и восстановление другого стека в том же потоке (“грязный хак”).
skavansНе уверен. Я давно не смотрел Stackless, но раньше у него основной поток блокировался при операциях ввода/вывода, а это как раз ваш случай. Если Stackless не научился работать без блокировок I/O, то вам нужно будет самому решать эту задачу поверх Stackless.
Stackless Python со своими тасклетами способен дать реально более высокую скорость работы, чем тот же gevent? Или не стоит заморачиваться в данной задаче?
Офлайн
А в чем различия между вот таким переключением зеленого потока и асинхронностью? Везде же некоторое подобие event loop используется, я прав?
Ну то есть когда мы делаем что-то “псевдопараллельно” в одном потоке ОС (асинхронность или green threads), то программе как-то нужно передавать управление между этими псевдопараллельными операциями. В случае зеленых потоков - переключать контекст, в случае асинхронности - дергать коллбеки. Или тут асинхронность может дать выигрыш в том плане, что проверить ready сокета быстрее, чем контекст переключить? Наверное так, да. В любом случае ближе к ночи смогу потестировать все предложенные варианты.
Задача, конечно, не академическая, но во-первых хочется полностью понять все 3 технологии, их плюсы и минусы, а во-вторых мне действительно важна скорость.
Только вот я когда пробовал обычные асинхронные сокеты использовать - сразу не завелось, т.к., насколько я понял, setblocking(0) и settimeout - взаимоисключающие операции, а метод без таймаутов мне не подходит. Получилось или мгновенно поставить все сокеты в состояния открытия соединения (setblocking only), или же получалась синхронная модель при последовательном вызове обоих методов. Или тут надо специальный фреймворк применить, а не обычный setblocking? Может быть poll/epoll или select? Вот в данные технологии пока не вникал полностью)
Многопроцессорность использовать пока не хочу, т.к. Хочется для началаьвыжать все из одного ядра, а потом уже можно и запустить такой псевдопараллельный скрипт в отдельности на каждом ядре - тогда это будет действительно предел возможностей
Спасибо всем за ответы, очень интересно) надеюсь, что этот тред будет одним из мест, где люди смогут почерпнуть информацию по сравнению данных техник, ибо смущает тот факт, что их куча, а отличия не вполне понятны)
Отредактировано skavans (Янв. 23, 2014 14:49:27)
Офлайн
skavansАсинхронные операции могут обслуживаться несколькими потоками и процессами.
А в чем различия между вот таким переключением зеленого потока и асинхронностью? Везде же некоторое подобие event loop используется, я прав?
Офлайн
Наконец-то получилось произвести замеры скорости. Итак, получилась следующая картина:
gevent - оптимальная емкость пула 2000, средняя скорость выполнения скрипта 10с
eventlet - оптимальная емкость пула 2500, средняя скорость выполнения скрипта 7с
eventlet по каким-то причинам получился быстрее.. ниже привожу код, который я прогонял
Eventlet
from iptools import IpRange import eventlet from eventlet.green import socket from datetime import datetime import sys POOL_CAPACITY = int(sys.argv[1]) iprange = iter(IpRange('62.64.64.0', '62.64.127.255')) def connect(): while True: c = socket.socket() c.settimeout(1) try: host = next(iprange) c.connect((host, 80)) # print 'connected' except StopIteration: break except Exception as e: pass # print 'error host!' finally: c.close() pool = eventlet.GreenPool(POOL_CAPACITY) start = datetime.now() for i in xrange(POOL_CAPACITY): pool.spawn_n(connect) pool.waitall() print (datetime.now() - start).total_seconds()
import gevent import sys from datetime import datetime from gevent import socket, pool from iptools import IpRange POOL_CAPACITY = int(sys.argv[1]) iprange = iter(IpRange('62.64.64.0', '62.64.127.255')) def task(): while True: c = socket.socket() c.settimeout(1) try: host = next(iprange) c.connect((host, 80)) # print 'connected' except StopIteration: break except Exception as e: pass # print 'error host!' finally: c.close() p = pool.Pool(POOL_CAPACITY) start = datetime.now() jobs = [p.spawn(task) for i in xrange(POOL_CAPACITY)] gevent.joinall(jobs) print (datetime.now() - start).total_seconds()
Отредактировано skavans (Янв. 26, 2014 22:55:55)
Офлайн