Найти - Пользователи
Полная версия: проблема threading
Начало » Python для новичков » проблема threading
1 2
Galvanize
Всем привет.

У меня возник насущный вопрос о кол-во одновременно работающих потоков. Я понимаю (может и не полностью) что такое GIL, и то, что 100, 200, 1000 потоков в питоне не будут работать нормально и просто зависнут.
Ко мне часто обращаются люди с просьбой написать всякие сканеры портов, которые смогут работать “ОЧЕНЬ БЫСТРО”, и на мой вопрос “насколько быстро” отвечают “ну, потоков в 500-1000”. Я говорю, что это невозможно, а они приводят мне какие-то доводы о том, что какой-то другой автор смог такое сделать. Я конечно же, сразу думаю, что человека обманули, т.к. скорее всего там нет параллельной работы сразу всех потоков, а стоит некий пул, из которого эти потоки по очереди запускаются.
Так вот, я хотел бы уточнить как оно на самом деле бывает, и если все-таки там пул, то где можно посмотреть вразумительный пример на эту тему.

И ещё такой вопрос:
будет ли лучше сделать асинхронный однопоточный сканер, вместо многопоточного?

Всем плюсую, даже за ответы вида “ты что, дурак?” =)

Singularity
ты что, дурак?
Galvanize
Нужен просто оптимальный способ запускать 500 потоков, даже если они не будут работать параллельно и все вместе. Простой пример пула, если возможно, приведите.
py.user.next
threading не даёт скорости, просто изображает параллельную работу
для скорости используют multiprocessing

python.org. multiprocessing
python.org. примеры

Galvanize
и если все-таки там пул, то где можно посмотреть вразумительный пример на эту тему
всё можно посмотреть в исходниках
doza_and
py.user.next
threading не даёт скорости, просто изображает параллельную работу
Это настоящие потоки. Просто питоновский код слишком часто синхронизируется. Если вы будете запускать из питона долго работающие скомпилированные модули то получите значительное ускорение от применения threading. multiprocessing от threading отличается организацией доступа к общим данным. Другое дело что 1000 потоков не имеют смысла. У вас на машине сколько ядер? ну 8 может 16. Грубо говоря надо 8 потоков или процессов, а остальное все за счет asincio http://docs.python.org/3.4/library/asyncio.html.

Вы сами легко сами можете заметить потребность в multiprocessing или threading. Посмотрите загрузку процессора. Как полностью загрузите одно ядро, отстреливайте второй процесс.
JOHN_16
Galvanize
не будут работать нормально и просто зависнут.
не правда. Сам запускал более 100 потоков (из академических целей). Да и в крайнем случае это уже вопрос не к Питону а к ОС.
Galvanize
Ко мне часто обращаются люди с просьбой написать всякие сканеры портов, которые смогут работать “ОЧЕНЬ БЫСТРО”, и на мой вопрос “насколько быстро” отвечают “ну, потоков в 500-1000”. Я говорю, что это невозможно, а они приводят мне какие-то доводы о том, что какой-то другой автор смог такое сделать. Я конечно же, сразу думаю, что человека обманули, т.к. скорее всего там нет параллельной работы сразу всех потоков, а стоит некий пул, из которого эти потоки по очереди запускаются.
вы не правы. У вас абсолютно нету тонкого понимания особенностей работы потоков и процессов. Тут уже говорили, но я по другому кратко изложу - потоки и процессы распараллеливают код, потоки удобны коммуникацией межу основным потоком и дочерними, но они не приспособлены к нагруженным действиям. Процессы это изолированный код , поэтому на них не распространяется GIL и как следствие они способны “держать” нагрузку.
К вопросу почему ЭТО возможно - узкое место в сетевых сканерах это операции потока ввода/вывода (простаивание в ожидании ответа) т.е. это не CPU высоконагруженные операции и как следствие GIL постоянно не переключается = производительность не падает.


Galvanize
JOHN_16
не правда. Сам запускал более 100 потоков (из академических целей). Да и в крайнем случае это уже вопрос не к Питону а к ОС.
Ну да, я тоже запускал. Но программа с большим кол-вом потоков не всегда завершается. Данные все собраны, но программа висит. Предполагаю, что они как-то блокируются во время работы и не завершаются. Возможно, что это из-за записи в файл, и блокировок на неё (RLock()).

JOHN_16
вы не правы. У вас абсолютно нету тонкого понимания особенностей работы потоков и процессов. Тут уже говорили, но я по другому кратко изложу - потоки и процессы распараллеливают код, потоки удобны коммуникацией межу основным потоком и дочерними, но они не приспособлены к нагруженным действиям. Процессы это изолированный код , поэтому на них не распространяется GIL и как следствие они способны “держать” нагрузку.
Тонкого понимания, разумеется, у меня нет. Я бы сюда не писал. О том, что потоки и процессы это способ распараллеливания это понятно, и то что процессы не подвержены gil я тоже знал. Но процессов должно быть по 2 на каждое ядро для комфортной работы, а это не совсем быстро.

doza_and
Galvanize
Но процессов должно быть по 2 на каждое ядро для комфортной работы, а это не совсем быстро.
Чтобы вы этим хотели сказать? Комфортной работы ядер? Разработчика? Не совсем быстро по отношению к чему? Приведите 2 кода один быстрый а другой не совсем… Тогда можно будет и разбираться.

Если виснет тоже шлите код. Я тоже пробовал много тредов запускать, ничего не виснет. Это проблема в логике синхронизации.
o7412369815963
Galvanize
будет ли лучше сделать асинхронный однопоточный сканер, вместо многопоточного?
Да.
Часто работает правило: есть много открытых соединений (в один момент времени)? - используй асинхронность.
Вот пример загрузчика на торнадо, там можете выставить 500, 1000, 2000 :)
Когда процесс будет упираться в ядро (100% нагрузки), то смысла увеличивать кол-во “потоков” нет и нужно распаралелливать на процессы.
py.user.next
doza_and
Если вы будете запускать из питона долго работающие скомпилированные модули то получите значительное ускорение от применения threading.
не, я запущу обычную работу со списком

def f(lst):
    lst.append(1)

из-за того что он не является потокобезопасным, GIL не даёт писать в него двум потокам сразу

#!/usr/bin/env python3
 
import timeit
import threading
 
def lf(lst):
    lst.append(1)
 
def f1():
    lst = []
    lf(lst)
    lf(lst)
 
def f2():
    lst = []
    lf(lst)
    t = threading.Thread(target=lf, args=(lst,))
    t.start()
    t.join()
 
def main():
    t1 = timeit.Timer('f1()', 'from __main__ import f1')
    t2 = timeit.Timer('f2()', 'from __main__ import f2')
 
    for t in t1, t2:
        print(t.repeat(3, 10000))
 
if __name__ == '__main__':
    main()

[guest@localhost py]$ ./thread_сmp.py 
[0.008367057002033107, 0.008361406002222793, 0.008245788998465287]
[2.177827506999165, 2.260574555999483, 2.182044921999477]
[guest@localhost py]$

habra. разбор от Бизли


add
даже вот так, снизив затраты на вызовы
#!/usr/bin/env python3
 
import timeit
import threading
 
def lf(lst):
    for i in range(10000):
        lst.append(i)
 
def f1():
    lst = []
    lf(lst)
    lf(lst)
 
def f2():
    lst = []
    lf(lst)
    t = threading.Thread(target=lf, args=(lst,))
    t.start()
    t.join()
 
def main():
    t1 = timeit.Timer('f1()', 'from __main__ import f1')
    t2 = timeit.Timer('f2()', 'from __main__ import f2')
 
    for t in t1, t2:
        print(t.repeat(3, 100))
 
if __name__ == '__main__':
    main()

[guest@localhost py]$ ./thread_сmp.py 
[0.31656129700058955, 0.3210945509999874, 0.32146822299910127]
[0.916342791999341, 0.9165670609982044, 0.8958057629970426]
[guest@localhost py]$
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