51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 705|回复: 0
打印 上一主题 下一主题

[python] 多线程和线程池的使用方法之Python

[复制链接]
  • TA的每日心情
    无聊
    昨天 09:05
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2023-6-25 13:41:19 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
     Python是一种高级编程语言,它在众多编程语言中,拥有极高的人气和使用率。Python中的多线程和线程池是其强大的功能之一,可以让我们更加高效地利用CPU资源,提高程序的运行速度。本篇博客将介绍Python中多线程和线程池的使用方法,并提供一些实用的案例供读者参考。
      一、多线程
      多线程是指在同一进程中,有多个线程同时执行不同的任务。Python中的多线程是通过threading模块来实现的。下面是一个简单的多线程示例:
      import threading
      def task(num):
          print('Task %d is running.' % num)
      if __name__ == '__main__':
          for i in range(5):
              t = threading.Thread(target=task, args=(i,))
              t.start()


      上述代码中,我们定义了一个task函数,它接受一个参数num,用于标识任务。在主程序中,我们创建了5个线程,每个线程都执行task函数,并传入不同的参数。通过start()方法启动线程。运行上述代码,可以看到输出结果类似于下面这样:
      Task 0 is running.
      Task 1 is running.
      Task 2 is running.
      Task 3 is running.
      Task 4 is running.


      由于多线程是并发执行的,因此输出结果的顺序可能会有所不同。
      二、线程池
      线程池是一种管理多线程的机制,它可以预先创建一定数量的线程,并将任务分配给这些线程执行。Python中的线程池是通过ThreadPoolExecutor类来实现的。下面是一个简单的线程池示例:
      import concurrent.futures
      def task(num):
          print('Task %d is running.' % num)
      if __name__ == '__main__':
          with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
              for i in range(5):
                  executor.submit(task, i)


      上述代码中,我们使用了with语句创建了一个ThreadPoolExecutor对象,其中max_workers参数指定了线程池中最大的线程数量。在主程序中,我们创建了5个任务,每个任务都通过executor.submit()方法提交给线程池执行。运行上述代码,可以看到输出结果类似于下面这样:
      Task 0 is running.
      Task 1 is running.
      Task 2 is running.
      Task 3 is running.
      Task 4 is running.


      由于线程池中最大的线程数量为3,因此只有3个任务可以同时执行,其他任务需要等待线程池中的线程空闲后再执行。
      三、使用案例
      下面是一个实际的案例,展示了如何使用多线程和线程池来加速数据处理过程。假设我们有一个包含1000个元素的列表,需要对每个元素进行某种运算,并将结果保存到另一个列表中。我们可以使用单线程的方式来实现:
      def process(data):
          result = []
          for item in data:
              result.append(item * 2)
          return result
      if __name__ == '__main__':
          data = list(range(1000))
          result = process(data)
          print(result)


      上述代码中,我们定义了一个process函数,它接受一个列表作为参数,对列表中的每个元素进行运算,并将结果保存到另一个列表中。在主程序中,我们创建了一个包含1000个元素的列表,并将其传递给process函数。运行上述代码,可以看到输出结果类似于下面这样:
      [0, 2, 4, 6, 8, ..., 1996, 1998]

      Python中的多线程和线程池可以提高爬虫的效率,本文将介绍一个爬取豆瓣电影Top250的案例,并通过多线程和线程池优化爬取过程。
      单线程爬取
      首先,我们先来看一下单线程爬取的代码:
      # -*- coding: utf-8 -*-
      import requests
      from bs4 import BeautifulSoup
      def get_html(url):
          try:
              response = requests.get(url)
              if response.status_code == 200:
                  return response.text
              else:
                  return None
          except Exception as e:
              print(e)
      def parse_html(html):
          soup = BeautifulSoup(html, 'lxml')
          movie_list = soup.find(class_='grid_view').find_all('li')
          for movie in movie_list:
              title = movie.find(class_='title').string
              rating = movie.find(class_='rating_num').string
              print(title, rating)
      def main():
          url = 'https://movie.douban.com/top250'
          html = get_html(url)
          parse_html(html)
      if __name__ == '__main__':
          main()


      这是一个简单的爬取豆瓣电影Top250的代码,首先通过requests库获取网页的HTML代码,然后使用BeautifulSoup库解析HTML代码,获取电影名称和评分。
      但是,这种单线程爬取的方式效率较低,因为在获取HTML代码的时候需要等待响应,而在等待响应的过程中CPU会空闲,无法充分利用计算机的性能。
      多线程爬取
      接下来,我们通过多线程的方式来优化爬取过程。首先,我们需要导入Python中的threading库:
      import threading

      然后,我们将获取HTML代码的代码放在一个函数中,并将其作为一个线程来运行:
      def get_html(url):
          try:
              response = requests.get(url)
              if response.status_code == 200:
                  return response.text
              else:
                  return None
          except Exception as e:
              print(e)
      class GetHtmlThread(threading.Thread):
          def __init__(self, url):
              threading.Thread.__init__(self)
              self.url = url
          def run(self):
              html = get_html(self.url)
              parse_html(html)


      在上面的代码中,我们首先定义了一个GetHtmlThread类,继承自threading.Thread类,然后在类的构造函数中传入需要爬取的URL。在run方法中,我们调用get_html函数获取HTML代码,并将其传入parse_html函数中进行解析。
      接下来,我们通过循环创建多个线程来进行爬取:
      def main():
          urls = ['https://movie.douban.com/top250?start={}'.format(i) for i in range(0, 250, 25)]
          threads = []
          for url in urls:
              thread = GetHtmlThread(url)
              thread.start()
              threads.append(thread)
          for thread in threads:
              thread.join()


      在上面的代码中,我们首先定义了一个urls列表,包含了所有需要爬取的URL。然后通过循环创建多个GetHtmlThread线程,并将其加入到threads列表中。最后,通过循环调用join方法等待所有线程执行完毕。
      通过多线程的方式,我们可以充分利用计算机的性能,提高爬取效率。
      线程池爬取
      在多线程的方式中,我们需要手动创建和管理线程,这样会增加代码的复杂度。因此,我们可以使用Python中的线程池来进行优化。
      首先,我们需要导入Python中的concurrent.futures库:
      import concurrent.futures

      然后,我们将获取HTML代码的代码放在一个函数中,并将其作为一个任务来提交给线程池:
      def get_html(url):
          try:
              response = requests.get(url)
              if response.status_code == 200:
                  return response.text
              else:
                  return None
          except Exception as e:
              print(e)
      def parse_html(html):
          soup = BeautifulSoup(html, 'lxml')
          movie_list = soup.find(class_='grid_view').find_all('li')
          for movie in movie_list:
              title = movie.find(class_='title').string
              rating = movie.find(class_='rating_num').string
              print(title, rating)
      def main():
          urls = ['https://movie.douban.com/top250?start={}'.format(i) for i in range(0, 250, 25)]
          with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
              futures = [executor.submit(get_html, url) for url in urls]
          for future in concurrent.futures.as_completed(futures):
              html = future.result()
              parse_html(html)


      在上面的代码中,我们首先定义了一个urls列表,包含了所有需要爬取的URL。然后通过with语句创建一个线程池,并设置最大线程数为5。接下来,我们通过循环将每个URL提交给线程池,并将返回的Future对象加入到futures列表中。最后,通过concurrent.futures.as_completed函数来等待所有任务执行完毕,并获取返回值进行解析。
      通过线程池的方式,我们可以更加简洁地实现多线程爬取,并且可以更加灵活地控制线程的数量,避免线程过多导致系统负载过高的问题。

    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-22 09:47 , Processed in 0.063444 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表