Как получить более высокую скорость при использовании многопоточности в 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 на нескольких поисковых площадках.

Сбросить сон () - это не помогает.

Другие вопросы по тегам