【问题标题】:How to get a faster speed when using multi-threading in python在python中使用多线程时如何获得更快的速度
【发布时间】:2012-04-26 14:58:12
【问题描述】:

现在我正在研究如何尽快从网站获取数据。为了获得更快的速度,我正在考虑使用多线程。这是我用来测试多线程和简单post的区别的代码。

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

正如您所见,这是一个非常简单的代码。首先我将模式设置为“简单”,我可以得到时间间隔:50s(可能我的速度有点慢:()。然后我将模式设置为“多个”,我得到时间间隔:35。从中我可以看到,多线程实际上可以提高速度,但结果并没有我想象的那么好。我想获得更快的速度。

从调试中发现程序主要阻塞在open_url = urllib2.urlopen(req, self.data)这一行,这行代码从指定网站发布和接收数据需要花费大量时间。我想也许我可以通过添加time.sleep() 并在urlopen 函数中使用多线程来获得更快的速度,但我不能这样做,因为它是python 自己的函数。

如果不考虑服务器阻止发布速度的可能限制,我还能做些什么来获得更快的速度?或者我可以修改的任何其他代码?非常感谢!

【问题讨论】:

  • 线程在 python 中是一个坏主意,它很容易成为瓶颈并且可能被 GIL 困住,尝试多处理。
  • @JakobBowyer:线程是这里的实现细节,真正的重点是打开多个连接。 Python 中线程的 GIL 方面在这里没有任何作用。
  • @nightcracker,你真的应该在发表这样的声明之前阅读 GIL 和线程......从这里开始:PyCon 2010: Understanding the Python GIL

标签: python multithreading post tcp


【解决方案1】:

你做错的最大的事情,就是对你的吞吐量影响最大,就是你打电话给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现有的线程是什么,都很糟糕。我们应该推荐多处理;看我的回答。
  • @Mike:这根本不是增量改进;使用提供的代码 MarkZar,它将我的测试中的运行时间从大约 20 秒缩短到不到半秒。这是有道理的,因为 http 使用最少的 CPU 但对网络延迟高度敏感,因此使用 threading 而不是 multiprocessing 是一个完全合理的解决方案。如果使用 Keep-Alive http 客户端(在我的固定线程测试中,urlib3urllib2 快大约 30%,否则没有任何改进),这将翻倍,这将无法跨进程使用。
  • @TokenMacGuy,python 中的 HTTP 在解析查询时会占用大量 CPU。这真的不是重点,正如大卫比兹利的演讲非常清楚的那样。 Python 中的线程之间没有很好的调度解决方案……正如您所见,多处理比 Python 线程快得多。
  • @user1121352,没错...我用 data 来证明多处理与线程的合理性...我不只是使用他的演示文稿
【解决方案2】:

在许多情况下,python 的线程并不能很好地提高执行速度……有时,它会使情况变得更糟。有关详细信息,请参阅David Beazley's PyCon2010 presentation on the Global Interpreter Lock / Pycon2010 GIL slides。此演示文稿内容丰富,我强烈推荐给任何考虑线程的人...

尽管 David Beazley 的演讲解释了网络流量改进了 Python 线程模块的调度,但您应该使用 multiprocessing module。我将此作为选项包含在您的代码中(请参阅我的答案的底部)。

在我的一台旧机器(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
            )

【讨论】:

  • 非常感谢。多处理是个好主意,它确实比我电脑上的多线程快一点。谢谢大家。我从这个问题中学到了很多东西。
  • @MarkZar,我想说 33% 的速度提升不止一点点,但无论如何我希望你的项目顺利。
  • 在我的一个代码上,它只是使用 pyexcel_ods 库并基于 200 个线程/进程(如果简单模式为 1 个)详细说明 .ods 文件,类似的行为给出:简单 = 16s 多个 = 28s ( ???) Process = 6s 谢谢你。
【解决方案3】:

请记住,在 Python 中,多线程可以“提高速度”的唯一情况是当您的操作像这样的 严重 I/O 绑定时。否则,多线程不会提高“速度”,因为它不能在多个 CPU 上运行(不,即使你有多个内核,python 也不能那样工作)。当您希望同时完成两件事时,您应该使用多线程,而不是当您希望两件事并行时(即两个进程分开运行)。

现在,您实际执行的操作实际上不会提高任何单个 DNS 查找的速度,但它会允许在等待其他一些请求的结果时触发多个请求,但您应该注意如何你做的很多,否则你只会让响应时间比现在更糟。

另外请停止使用 urllib2,使用 Requests:http://docs.python-requests.org

【讨论】:

    【解决方案4】:

    DNS 查找需要时间。你对此无能为力。大延迟是首先使用多个线程的一个原因 - 多个查找广告网站 GET/POST 可以并行发生。

    转储 sleep() - 它没有帮助。

    【讨论】:

    • 谢谢,但我只是困惑为什么time.sleep() 没用。确实,转储sleep()后也能正常工作,但是没有sleep()怎么实现多线程呢? python会自动以随机间隔运行不同的线程吗?如果有,sleep() 函数有什么用?
    • 这不是没用,只是在这里不合适。使用睡眠 - 有负载。 '打开泵后,至少等待十秒钟让压力稳定后再打开进料阀'。
    猜你喜欢
    • 2022-06-12
    • 1970-01-01
    • 1970-01-01
    • 2012-08-14
    • 1970-01-01
    • 2021-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多