Найти - Пользователи
Полная версия: Network. GIL, threads и green threads.
Начало » Python для экспертов » Network. GIL, threads и green threads.
1 2 3
skavans
Всем доброго времени!

Возникла задача реализовать некоторое подобие сканера портов по большому диапазону 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, то все было бы супер-классно, но увы
JOHN_16
1) Правильно.
skavans
Проблема решается и тем, и другим подходом, однако хочется наибольшую скорость при наименьших затратах ресурсов.
Раз у вас есть 2 рабочих кода- сравните их и по скорости, и по ресурсам. Отпишитесь о результатах.
skavans
Завтра обязательно сравню и отпишусь, сегодня проблема со временем, поэтому решил для начала спросить совета
Помимо практических тестов, хочется досконально разобраться в данных технологиях, поэтому вопросы 2-5 все равно актуальны)
Lexander
Время позднее, поэтому я коротко.
Потоки ограничены 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. Идет переключение контекста, скорее всего. Т.е. на обслуживание потоков в какой-то момент начинает тратиться больше времени, чем на выполнение работы в потоках.
skavans
Lexander, спасибо за ответ)
Да, и еще такой вопрос: Stackless Python со своими тасклетами способен дать реально более высокую скорость работы, чем тот же gevent? Или не стоит заморачиваться в данной задаче?
o7412369815963
Для большого кол-ва открытых соединений лучше использовать зеленые потоки либо асинхронность (до мульти-процессов), везде пишут что обычные потоки хавают много памяти (но я бы не сказал, что они много памяти хавают - для теста создавал много потоков, замерял память).

Lexander
2. treading создает поток внутри процесса Питона, green thread - это то же самое.
зеленые потоки не создают доп. потоков, они работают в одном потоке, переключение происходит сохранением стека и восстановление другого стека в том же потоке (“грязный хак”).

Если зеленых потоков очень много и они часто переключаются, то они могут сильно нагружать CPU (из за переключения стеков). Если нагрузка зашкаливает, то можно попробовать асинхронный фреймворк (например tornado), там “переключение” происходит “естественно”, и в теории нагрузки будет заметно меньше. Но проще заюзать много-процессорность.

skavans
однако не очень понял, почему на 2000 микропотоков и на 3000 прироста в скорости не ощущается? Трафик идет порядка 300 кб/сек, загрузка процессора порядка 30%,
это загрузка всего процессора? сколько нагрузка на одно ядро? какую нагрузку создает само приложение?
Попробуйте запустить 2-4 копии приложения одновременно.

skavans
Stackless Python со своими тасклетами способен дать реально более высокую скорость работы, чем тот же gevent?
на сколько я знаю, green thread - это попытка скопировать тасклеты из Stackless Python, т.е. то же самое, но в stackless они сделаны по нормальному - на “уровне ядра”.
Но я бы не стал с ним заморачиваться, с таким же успехом можно взять ерланг или ещё что-нибудь.
Lexander
o7412369815963
зеленые потоки не создают доп. потоков, они работают в одном потоке, переключение происходит сохранением стека и восстановление другого стека в том же потоке (“грязный хак”).
Да, именно так. Я же написал “поток” - в единственном числе.
Вернее, почти так. Зависит от деталей реализации.
Например, вместо одного потока может быть несколько, но принцип остается такой же.
Хак? Да. На счет “грязного” есть нюансы. Принцип заимствован из main event loop - самый обычный способ написания нативных приложений, обеспечивающий многозадачность.
В этом смысле Питон выступает виртуальной машиной, внутри которой свои потоки и ими можно управлять аналогично потокам ОС.
skavans
Stackless Python со своими тасклетами способен дать реально более высокую скорость работы, чем тот же gevent? Или не стоит заморачиваться в данной задаче?
Не уверен. Я давно не смотрел Stackless, но раньше у него основной поток блокировался при операциях ввода/вывода, а это как раз ваш случай. Если Stackless не научился работать без блокировок I/O, то вам нужно будет самому решать эту задачу поверх Stackless.
Если бы задача была академического характера или регулярной, я бы протестировал все 4 варианта и выбрал бы лучший по моим критериям, если задача практическая редкоповторяющаяся, то я бы не заморачивался тестами при сравнении.
skavans
А в чем различия между вот таким переключением зеленого потока и асинхронностью? Везде же некоторое подобие event loop используется, я прав?
Ну то есть когда мы делаем что-то “псевдопараллельно” в одном потоке ОС (асинхронность или green threads), то программе как-то нужно передавать управление между этими псевдопараллельными операциями. В случае зеленых потоков - переключать контекст, в случае асинхронности - дергать коллбеки. Или тут асинхронность может дать выигрыш в том плане, что проверить ready сокета быстрее, чем контекст переключить? Наверное так, да. В любом случае ближе к ночи смогу потестировать все предложенные варианты.
Задача, конечно, не академическая, но во-первых хочется полностью понять все 3 технологии, их плюсы и минусы, а во-вторых мне действительно важна скорость.
Только вот я когда пробовал обычные асинхронные сокеты использовать - сразу не завелось, т.к., насколько я понял, setblocking(0) и settimeout - взаимоисключающие операции, а метод без таймаутов мне не подходит. Получилось или мгновенно поставить все сокеты в состояния открытия соединения (setblocking only), или же получалась синхронная модель при последовательном вызове обоих методов. Или тут надо специальный фреймворк применить, а не обычный setblocking? Может быть poll/epoll или select? Вот в данные технологии пока не вникал полностью)
Многопроцессорность использовать пока не хочу, т.к. Хочется для началаьвыжать все из одного ядра, а потом уже можно и запустить такой псевдопараллельный скрипт в отдельности на каждом ядре - тогда это будет действительно предел возможностей

Спасибо всем за ответы, очень интересно) надеюсь, что этот тред будет одним из мест, где люди смогут почерпнуть информацию по сравнению данных техник, ибо смущает тот факт, что их куча, а отличия не вполне понятны)
Lexander
skavans
А в чем различия между вот таким переключением зеленого потока и асинхронностью? Везде же некоторое подобие event loop используется, я прав?
Асинхронные операции могут обслуживаться несколькими потоками и процессами.
Зависит от конкретной реализации.
Проверять текущее состояние всегда быстрее, не требуется выгрузка/загрузка стека.
Переключение контекста можно грубо сравнить с переключением между окнами разных программ.
skavans
Наконец-то получилось произвести замеры скорости. Итак, получилась следующая картина:
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()

Gevent
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()

Может что-то не так в коде? Что-то можно еще оптимизировать? Думаю, у вас есть положительный опыт асинхронного программирования, укажите на ошибки, пожалуйста.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB