Day 16 16.2 并发爬虫二、多线程实现

并发爬虫二、多线程实现

【1】threading模块

  • Python提供两个模块进行多线程的操作,分别是threadthreading,前者是比较低级的模块,用于更底层的操作,一般应用级别的开发不常用。
import time def foo(): print("foo start...") time.sleep(5) print("foo end...") def bar(): print("bar start...") time.sleep(3) print("bar end...") # 串行版本 # start = time.time() # foo() # bar() # end = time.time() # print("cost timer:", end - start) # 多线程并发版本 import threading start = time.time() t1 = threading.Thread(target=foo, args=()) t1.start() t2 = threading.Thread(target=bar, args=()) t2.start() # 等待所有子线程结束 # t1.join() # 等待子线程t1 # t2.join() # 等待子线程t2 end = time.time() print(end - start)

【2】互斥锁

import time import threading Lock = threading.Lock() def addNum(): global num # 在每个线程中都获取这个全局变量 # 上锁 Lock.acquire() t = num - 1 time.sleep(0.0001) num = t Lock.release() # 放锁 num = 100 # 设定一个共享变量 thread_list = [] for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: # 等待所有线程执行完毕 t.join() print('Result: ', num)

【3】线程池

  • 系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。

    • 在这种情形下,使用线程池可以很好地提升性能,
    • 尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。
  • 线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。

    • 当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。
  • 此外,使用线程池可以有效地控制系统中并发线程的数量。

    • 当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致解释器崩溃,
    • 而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。
import time from concurrent.futures import ThreadPoolExecutor def task(i): print(f'任务{i}开始!') time.sleep(i) print(f'任务{i}结束!') return i start = time.time() pool = ThreadPoolExecutor(3) future01 = pool.submit(task, 1) # print("future01是否结束", future01.done()) # 当程序使用 Future 的 result() 方法来获取结果时,该方法会阻塞当前线程,如果没有指定 timeout 参数,当前线程将一直处于阻塞状态,直到 Future 代表的任务返回。 # print("future01的结果", future01.result()) # 同步等待 future02 = pool.submit(task, 2) future03 = pool.submit(task, 3) pool.shutdown() # 阻塞等待 print(f"程序耗时{time.time() - start}秒钟") print("future01的结果", future01.result()) print("future02的结果", future02.result()) print("future03的结果", future03.result())

使用线程池来执行线程任务的步骤如下:

  1. 调用 ThreadPoolExecutor 类的构造器创建一个线程池。
  2. 定义一个普通函数作为线程任务。
  3. 调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。
  4. 当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。

【4】线程应用

import requests from lxml import etree import os import asyncio import time import threading def get_img_urls(): res = requests.get("https://www.pkdoutu.com/photo/list/", headers={ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" }) selector = etree.HTML(res.text) img_urls = selector.xpath('//li[@class="list-group-item"]/div/div/a/img[@data-backup]/@data-backup') print(img_urls) return img_urls def save_img(url): res = requests.get(url) name = os.path.basename(url) with open("imgs/" + name, "wb") as f: f.write(res.content) print(f"{name}下载完成!") def main(): img_urls = get_img_urls() # 串行 [save_img(url) for url in img_urls] # 协程并发 t_list = [] for url in img_urls: t = threading.Thread(target=save_img, args=(url,)) t.start() t_list.append(t) for t in t_list: t.join() if __name__ == '__main__': start = time.time() main() end = time.time() print(end - start)

针对IO密集型任务,Python多线程可以发挥出不错的并发作用


__EOF__

本文作者Chimengmeng
本文链接https://www.cnblogs.com/dream-ze/p/17218094.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Chimengmeng  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示