筱团Blog筱团のBlog

python 高级用法 -- 进程、线程和协程

筱团·2022-09-14 15:59·173 次阅读

python 高级用法 -- 进程、线程和协程

Prerequisite#

参考文章:廖雪峰的文章【太复杂了】
参考视频:路飞学城【要钱的】

概念#

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个 Word 就启动了一个 Word 进程。
有些进程还不止同时干一件事,比如 Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

单核 CPU 操作系统轮流让各个任务交替执行,任务 1 执行 0.01 秒,切换到任务 2,任务 2 执行 0.01 秒,再切换到任务 3,执行 0.01 秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于 CPU 的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

多任务的实现有3种方式:

  • 多进程模式
  • 多线程模式
  • 多进程 + 多线程模式

线程是最小的执行单元,进程是最小的资源分配单位,而进程由至少一个线程组成;如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

协程,又称微线程,作用是在单线程遇到堵塞操作(I/O 操作、发送请求、等待响应)时反复切换任务(任务切换,并非线程切换)

小笔记#

子程序,或者称为函数,在所有语言中都是层级调用,比如 A 调用 B,B 在执行过程中又调用了 C,C 执行完毕返回,B 执行完毕返回,最后是 A 执行完毕

一个线程就是执行一个子程序

默认一个主函数只有一个进程,一个进程默认只有一个线程

多线程#

Copy
#! /usr/bin/env python # -*- coding: UTF-8 -*- from threading import Thread import requests import time def get_page(year): try: response = requests.get('https://www.baidu.com/' + str(year)) except Exception as e: pass if __name__ == '__main__': starttime = time.time() for y in range(2000, 2500): t = Thread(target=get_page, args=(y, )) t.start() endtime = time.time() print(endtime - starttime) # 多线程,2.27 秒 starttime = time.time() for y in range(2000, 2500): get_page(y) endtime = time.time() print(endtime - starttime) # 普通,52 秒

线程池#

在线程池中,仅当全部线程执行完了,才会继续执行下面的代码,这是个大好处

Copy
#! /usr/bin/env python # -*- coding: UTF-8 -*- from concurrent.futures import ThreadPoolExecutor import requests import time def get_page(year): try: response = requests.get('https://www.baidu.com/' + str(year)) except Exception as e: pass if __name__ == '__main__': starttime = time.time() with ThreadPoolExecutor(16) as t: for y in range(2000, 2500): t.submit(get_page, y) endtime = time.time() print(endtime - starttime) # 线程池,8 秒

多进程#

Copy
#! /usr/bin/env python # -*- coding: UTF-8 -*- from multiprocessing import Process import requests def get_page(year): try: response = requests.get('https://www.baidu.com/' + str(year)) except Exception as e: pass if __name__ == '__main__': p1 = Process(target=get_page, args=("2020", )) p2 = Process(target=get_page, args=("2021", )) p3 = Process(target=get_page, args=("2022", )) p1.start() p2.start() p3.start()

多进程 + 线程池#

  1. 当需要执行多个互相不打扰的函数时,这是个很好的模型,因为一个进程中可以跑多个线程
Copy
#! /usr/bin/env python # -*- coding: UTF-8 -*- from multiprocessing import Queue from multiprocessing import Process from concurrent.futures import ThreadPoolExecutor import requests import time def get_page(year): try: response = requests.get('https://www.baidu.com/' + str(year)) except Exception as e: pass def function1(q): with ThreadPoolExecutor(16) as t: for y in range(2000, 2250): t.submit(get_page, y) def function2(q): with ThreadPoolExecutor(16) as t: for y in range(2250, 2500): t.submit(get_page, y) if __name__ == '__main__': starttime = time.time() q = Queue() # 主进程,没实际作用 p1 = Process(target=function1, args=(q, )) p2 = Process(target=function2, args=(q, )) p1.start() p2.start() p1.join() # 主进程等待子进程跑完 p2.join() # 主进程等待子进程跑完 endtime = time.time() print(endtime - starttime) # 多进程 + 线程池,6 秒
  1. 当需要执行多个相互作用的函数时,这也是很好的模型,因为一个主进程中有多个子进程,子进程可以在主线程中相互作用
Copy
#! /usr/bin/env python # -*- coding: UTF-8 -*- from multiprocessing import Queue from multiprocessing import Process from concurrent.futures import ThreadPoolExecutor import requests from lxml import etree def get_img_url(q): try: for i in range(5): response = requests.get(f'https://www.10wallpaper.com/cn/List_wallpapers/page/{str(i)}') response.encoding = 'utf-8' et = etree.HTML(response.text) div_list = et.xpath("//div[@id='pics-list']/p") for div in div_list: # 图片 URL href = div.xpath("./a/@href")[0] href = "https://www.10wallpaper.com/wallpaper/medium/2209/" + href.split('.')[0].split('/')[3] + "_medium.jpg" # 图片标题 text = div.xpath("./a/span/text()")[0] text = text.replace(',', '-') q.put([href, text]) # 以列表的形式入队 q.put([None, None]) except Exception as e: pass def download(q): with ThreadPoolExecutor(16) as t: while 1: img_url, file_name = q.get() # 获取出队 q 传递的值 [图片 URL, 图片标题] if(img_url == None): break response = requests.get(img_url) with open("./img/" + file_name + ".jpg", mode="wb") as f: f.write(response.content) print(img_url, file_name) # 打印图片 URL 和图片标题,显示下载进度 if __name__ == '__main__': q = Queue() # 主进程 p1 = Process(target=get_img_url, args=(q, )) # 获取图片连接 p2 = Process(target=download, args=(q, )) # 下载图片文件 p1.start() p2.start() p1.join() # 主进程等待子进程跑完 p2.join() # 主进程等待子进程跑完 # PS:get_img_url 函数也可以单独分离出一个函数,用线程池运行

协程#

  1. 协程的标准写法
Copy
#! /usr/bin/env python # -*- coding: UTF-8 -*- import asyncio async def task1(): print("任务1开始") await asyncio.sleep(1) print("任务1完成") return "任务1顺利结束" async def task2(): print("任务2开始") await asyncio.sleep(2) print("任务2完成") return "任务2顺利结束" async def task3(): print("任务3开始") await asyncio.sleep(3) print("任务3完成") return "任务3顺利结束" async def main(): # 任务列表 tasks = [ asyncio.create_task(task3()), asyncio.create_task(task1()), asyncio.create_task(task2()), ] # 不需要返回值,直接运行任务,并等待全部任务跑完 await asyncio.wait(tasks) # 读取返回的结果在 result 中,方案一(推荐,按照任务添加的顺序返回结果) """ result = await asyncio.gather(*tasks, return_exceptions=True) for r in result: print(r) """ # 读取返回的结果在 result 中,方案二 """ result, pending = await asyncio.wait(tasks) for r in result: print(r.result()) """ if __name__ == '__main__': # 运行协程,方案一(推荐) lop = asyncio.get_event_loop() lop.run_until_complete(main()) # 运行协程,方案二 """ asyncio.run(main()) """
  1. 协程在爬虫中的运用(以下载图片为例)
Copy
#! /usr/bin/env python # -*- coding: UTF-8 -*- import aiohttp import asyncio import aiofiles async def download(url): try: file_name = url.split("/")[-1] # 网络请求 async with aiohttp.ClientSession() as session: async with session.get(url) as resp: # 读取页面源内容(byte) content = await resp.content.read() # 读取网页内容(html) """ page_source = await resp.text(encoding='utf-8') # 设置字符集 """ # 读取 JSON 数据(json) """ dic = await resp.json() """ # 写入文件 async with aiofiles.open(file_name, mode="wb") as f: await f.write(content) except Exception as e: pass async def main(): url_list = [ "https://www.10wallpaper.com/wallpaper/medium/2209/Brooke_Street_Pier_Travel_Hobart_Tasmania_Australia_5K_medium.jpg", "https://www.10wallpaper.com/wallpaper/medium/2209/Copper_Ridge_trail_North_Cascades_National_Park_USA_5K_medium.jpg" ] tasks = [] for url in url_list: # 创建任务,下载图片 task = asyncio.create_task(download(url)) tasks.append(task) await asyncio.wait(tasks) if __name__ == '__main__': lop = asyncio.get_event_loop() lop.run_until_complete(main())

总结#

  • 线程池
    • 需要反复执行单个函数
  • 多进程 + 线程池
    • 需要执行多个互相不打扰的函数时
    • 需要执行多个相互作用的函数时
  • 协程
    • 存在堵塞(网络请求、文件读写)时
posted @   筱团  阅读(173)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
点击右上角即可分享
微信分享提示
目录