Как получить более высокую скорость при использовании многопоточности в Python
Сейчас я изучаю, как получить данные с сайта как можно быстрее. Чтобы получить более высокую скорость, я рассматриваю возможность использования многопоточности. Вот код, который я использовал, чтобы проверить разницу между многопоточным и простым постом.
import threading
import time
import urllib
import urllib2
class Post:
def __init__(self, website, data, mode):
self.website = website
self.data = data
#mode is either "Simple"(Simple POST) or "Multiple"(Multi-thread POST)
self.mode = mode
def post(self):
#post data
req = urllib2.Request(self.website)
open_url = urllib2.urlopen(req, self.data)
if self.mode == "Multiple":
time.sleep(0.001)
#read HTMLData
HTMLData = open_url.read()
print "OK"
if __name__ == "__main__":
current_post = Post("http://forum.xda-developers.com/login.php", "vb_login_username=test&vb_login_password&securitytoken=guest&do=login", \
"Simple")
#save the time before post data
origin_time = time.time()
if(current_post.mode == "Multiple"):
#multithreading POST
for i in range(0, 10):
thread = threading.Thread(target = current_post.post)
thread.start()
thread.join()
#calculate the time interval
time_interval = time.time() - origin_time
print time_interval
if(current_post.mode == "Simple"):
#simple POST
for i in range(0, 10):
current_post.post()
#calculate the time interval
time_interval = time.time() - origin_time
print time_interval
как вы можете видеть, это очень простой код. Сначала я установил режим "Простой", и я могу получить интервал времени: 50 с(возможно, моя скорость немного медленная:(). Затем я установил режим "Несколько", и я получил интервал времени: 35. из того, что я вижу, многопоточность может на самом деле увеличить скорость, но результат не так хорош, как я себе представляю. Я хочу получить гораздо более высокую скорость.
из отладки я обнаружил, что программа в основном блокирует в строке: open_url = urllib2.urlopen(req, self.data)
эта строка кода занимает много времени для публикации и получения данных с указанного веб-сайта. Я думаю, может быть, я могу получить более высокую скорость, добавив time.sleep()
и используя многопоточность внутри urlopen
функция, но я не могу этого сделать, потому что это собственная функция питона.
Если не принимать во внимание возможные ограничения, что сервер блокирует скорость передачи, что еще я могу сделать, чтобы получить более высокую скорость? или любой другой код, который я могу изменить? большое спасибо!
4 ответа
Во многих случаях многопоточность Python не очень хорошо увеличивает скорость выполнения... иногда это ухудшает ситуацию. Для получения дополнительной информации см . Презентацию Дэвида Бизли PyCon2010 о слайдах Global Interpreter Lock / Pycon2010 GIL. Эта презентация очень информативна, я настоятельно рекомендую ее всем, кто интересуется многопоточностью...
Несмотря на то, что в выступлении Дэвида Бизли объясняется, что сетевой трафик улучшает планирование работы модуля потоков Python, вы должны использовать многопроцессорный модуль. Я включил это в качестве опции в ваш код (см. Нижнюю часть моего ответа).
Выполнение этого на одной из моих старых машин (Python 2.6.6):
current_post.mode == "Process" (multiprocessing) --> 0.2609 seconds
current_post.mode == "Multiple" (threading) --> 0.3947 seconds
current_post.mode == "Simple" (serial execution) --> 1.650 seconds
Я согласен с комментарием TokenMacGuy, и приведенные выше цифры включают перемещение .join()
в другой цикл. Как вы можете видеть, многопроцессорная обработка python значительно быстрее, чем многопоточность.
from multiprocessing import Process
import threading
import time
import urllib
import urllib2
class Post:
def __init__(self, website, data, mode):
self.website = website
self.data = data
#mode is either:
# "Simple" (Simple POST)
# "Multiple" (Multi-thread POST)
# "Process" (Multiprocessing)
self.mode = mode
self.run_job()
def post(self):
#post data
req = urllib2.Request(self.website)
open_url = urllib2.urlopen(req, self.data)
if self.mode == "Multiple":
time.sleep(0.001)
#read HTMLData
HTMLData = open_url.read()
#print "OK"
def run_job(self):
"""This was refactored from the OP's code"""
origin_time = time.time()
if(self.mode == "Multiple"):
#multithreading POST
threads = list()
for i in range(0, 10):
thread = threading.Thread(target = self.post)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
#calculate the time interval
time_interval = time.time() - origin_time
print "mode - {0}: {1}".format(method, time_interval)
if(self.mode == "Process"):
#multiprocessing POST
processes = list()
for i in range(0, 10):
process = Process(target=self.post)
process.start()
processes.append(process)
for process in processes:
process.join()
#calculate the time interval
time_interval = time.time() - origin_time
print "mode - {0}: {1}".format(method, time_interval)
if(self.mode == "Simple"):
#simple POST
for i in range(0, 10):
self.post()
#calculate the time interval
time_interval = time.time() - origin_time
print "mode - {0}: {1}".format(method, time_interval)
return time_interval
if __name__ == "__main__":
for method in ["Process", "Multiple", "Simple"]:
Post("http://forum.xda-developers.com/login.php",
"vb_login_username=test&vb_login_password&securitytoken=guest&do=login",
method
)
Самое большое, что вы делаете неправильно, это больше всего мешает вашей пропускной способности, это то, как вы звоните thread.start()
а также thread.join()
:
for i in range(0, 10):
thread = threading.Thread(target = current_post.post)
thread.start()
thread.join()
Каждый раз в цикле вы создаете поток, запускаете его и ждете, пока он не закончится, прежде чем переходить к следующему потоку. Вы вообще ничего не делаете одновременно!
Что вы, вероятно, должны делать вместо этого:
threads = []
# start all of the threads
for i in range(0, 10):
thread = threading.Thread(target = current_post.post)
thread.start()
threads.append(thread)
# now wait for them all to finish
for thread in threads:
thread.join()
Имейте в виду, что единственный случай, когда многопоточность может "увеличить скорость" в Python, - это когда у вас есть такие операции, которые сильно связаны с вводом / выводом. В противном случае многопоточность не увеличивает "скорость", поскольку она не может работать на более чем одном процессоре (нет, даже если у вас несколько ядер, python не работает таким образом). Вы должны использовать многопоточность, когда вы хотите, чтобы две вещи выполнялись одновременно, а не тогда, когда вы хотите, чтобы две вещи были параллельными (то есть два процесса, выполняющиеся отдельно).
Теперь то, что вы на самом деле делаете, на самом деле не увеличит скорость какого-либо отдельного поиска DNS, но позволит отбрасывать несколько запросов в ожидании результатов некоторых других, но вы должны быть осторожны с тем, сколько вы делаете или вы просто сделаете время отклика еще хуже, чем оно уже есть.
Также, пожалуйста, прекратите использовать urllib2 и используйте запросы: http://docs.python-requests.org/
Поиск DNS занимает много времени. Вы ничего не можете с этим поделать. Во-первых, большие задержки - это одна из причин использования нескольких потоков - параллельный поиск GET/POST на нескольких поисковых площадках.
Сбросить сон () - это не помогает.