多线程、协程和多进程并发编程(续写)

2023-12-23 14:02:00 浏览数 (1)

接上篇继续。

9 【案例】创建1个进程执⾏任务

python中的多线程其实并不是真正的多线程,如果想要充分地使⽤多核CPU的资源,在python中

⼤部分情况需要使⽤多进程,尤其是密集型计算任务

代码语言:python代码运行次数:0复制
import multiprocessing
import time
def worker(interval):
 print(f"The time is {time.ctime()}")
 for i in range(interval):
 # 做⼀些数值计算任务
 # ⾮常消耗cpu等
 print("---", sep='^')
if __name__ == "__main__":
 p = multiprocessing.Process(target = worker, args = (1000,))
 p.start()
 print("p.pid:", p.pid)
 print("p.name:", p.name)
 print("p.is_alive:", p.is_alive())

10 【案例】创建3个进程并⾏执⾏任务

将⼀个任务拆分为3个⼦任务,放到每个进程中并⾏执⾏

⼀个任务:计算从1到1500的整数的平⽅,并返回。

假定我们使⽤三个进程来做,可以将任务分为三部分: [1,500) , [500,1000) ,

[1000,1500) ,分别放到三个进程中:

代码语言:python代码运行次数:0复制
import multiprocessing
import time
task = list(range(1, 1501))
def subtask_1():
 print("subtask_1")
 subtask = task[:500]
 res = []
 for val in subtask:
 res.append(val**2)
 print("> end subtask_1")
def subtask_2():
 print("subtask_2")
 subtask = task[500:1000]
 res = []
 for val in subtask:
 res.append(val**2)
 print("> end subtask_2")
def subtask_3():
 print("subtask_3")
 subtask = task[1000:1500]
 res = []
 for val in subtask:
 res.append(val**2)
 print("> end subtask_3")
 
 
 if __name__ == "__main__":
 p1 = multiprocessing.Process(target = subtask_1)
 p2 = multiprocessing.Process(target = subtask_2)
 p3 = multiprocessing.Process(target = subtask_3)
 p1.start()
 p2.start()
 p3.start()

11 使⽤进程池⾼效管理多进程

当被操作对象数⽬不⼤时,可以直接利⽤multiprocessing中的Process动态成⽣多个进程,⼗⼏

个还好,但如果是上百个,上千个⽬标,⼿动的去限制进程数量却⼜太过繁琐,此时可以发挥进

程池的功效。

⽐如,在利⽤Python进⾏系统管理的时候,特别是同时操作多个⽂件⽬录,或者远程控制多台主

机,并⾏操作可以节约⼤量的时间。

Pool可以提供指定数量的进程,供⽤户调⽤,当有新的请求提交到pool中时,如果池还没有满,

那么就会创建⼀个新的进程⽤来执⾏该请求;

但如果池中的进程数已经达到规定最⼤值,那么该请求就会等待,直到池中有进程结束,才会创

建新的进程来执⾏它。

代码语言:python代码运行次数:0复制
import multiprocessing
task = list(range(1, 150001))
def worker(start, end, process_i):
 print(f"subtask_{process_i}")
 subtask = task[start:end]
 res = []
 for val in subtask:
 res.append(val ** 2)
 # print(res)
 print(f"> end subtask_{process_i}")
 # 进程池
if __name__ == "__main__":
 pool = multiprocessing.Pool(processes=3)
 n = 5
 start = 0
 end = step = 150000 // n
 for i in range(n):
 pool.apply_async(func=worker, args=(start, end, i   1))
 start, end = start   step, end   step
 pool.close()
 pool.join()
 print("programming done")

12 使⽤进程池并关注获取每个进程返回结果

代码语言:python代码运行次数:0复制
result.append(pool.apply_async(func, (msg, )))
for res in result:
 res.get()

13 使⽤Queue实现多进程之间的数据传递

代码语言:python代码运行次数:0复制
import multiprocessing
def writer_proc(q):
 q.put("write data to mysql")
def reader_proc(q):
 res = q.get()
 res2 = res.replace("write", "read").replace("to", "from")
 print(res2)
 
 if __name__ == "__main__":
 q = multiprocessing.Queue()
 writer = multiprocessing.Process(target=writer_proc, args=(q,))
 writer.start()
 reader = multiprocessing.Process(target=reader_proc, args=(q,))
 reader.start()
 reader.join()
 writer.join()

14 什么是协程?它和线程有什么区别?

协程,是运⾏在单个线程中的”并发“

协程与多线程相⽐,有哪些优势?

第⼀,使⽤协程,单个线程中就能做到并发执⾏IO任务;

⽽使⽤线程模型实现IO任务的并发,必须要创建多个线程,⽽多个线程的创建和切换都耗费⽐使

⽤协程更多的时间和资源。

这个区别是明显的,协程相⽐于多线程执⾏效率更⾼。

第⼆,协程何时执⾏、何处中断都完全受开发者的控制,⽽多线程启动后完全受操作系统的控

制,线程的终⽌也完全受操作系统控制。

15 如何创建⼀个协程并运⾏?

从Python3.5后,Python在函数或⽅法前添加async,函数或⽅法就变为⼀个协程。

如下所示,print_hello就是最简单的⼀个协程:

代码语言:python代码运行次数:0复制
async def print_hello():
 print('hello world')

直接调⽤print_hello函数,并没有打印出结果,⽽是显示协程对象,如下所示:

<coroutine object print_hello at 0x7fbbc96596c0>

要想运⾏⼀个协程,必须扔协程到asyncio的run⽅法中,如下所示:

代码语言:python代码运行次数:0复制
import asyncio
asyncio.run(print_hello())

执⾏后,才能正常打印结果:hello world

16 【案例】编程实现多协程并发执⾏任务

代码语言:python代码运行次数:0复制
import asyncio
async def cook_food1():
 print("开始炒地三鲜")
 await asyncio.sleep(3)
 print("地三鲜出锅")
 
 async def cook_food2():
 print("开始炒回锅⾁")
 await asyncio.sleep(3)
 print("回锅⾁出锅")
 
 if __name__ == "__main__":
 event_loop = asyncio.get_event_loop()
 event_loop.run_until_complete(asyncio.gather(cook_food1(), cook_food2()))
 event_loop.close()

17 【案例】协程实现多任务异步爬⾍案例

使⽤异步web请求框架 aiohttp ,实现异步爬取多个⽹⻚。

如下所示,共使⽤以下模块:

代码语言:python代码运行次数:0复制
import datetime
import asyncio
import aiohttp

使⽤ async 创建协程 crawler_url ,它的第⼀个参数是客户端的session,使⽤ aiohttp

的 ClientSession 创建;第⼆个参数是带爬取的 url

await 实现发起异步请求 url ⽹⻚,同时分别打印 await 前的时间,以及打印获得响应后的

时间,如下所示:

代码语言:python代码运行次数:0复制
async def crawler_url(session, url):
 print(f"{datetime.datetime.now().strftime('%H:%M:%S')} 开始请求 {url}")
 resp = await session.request(method="GET", url=url)
 print(f"{datetime.datetime.now().strftime('%H:%M:%S')} GET {url} 

async_crawler 协程实现多任务异步爬取,如下所示,分别创建爬取两个⽹址的对应任务

task1 、 task2 ,最后在这个线程中等待所有任务结束,程序才终⽌:

代码语言:python代码运行次数:0复制
async def async_crawler():
 tasks = []
 async with aiohttp.ClientSession() as session:
 task1 = crawler_url(session, 'https://docs.python.org/zh-cn/3/library
 task2 = crawler_url(session, 'http://www.zglg.work/')
 tasks.append(task1)
 tasks.append(task2)
 await asyncio.gather(*tasks)

下⾯执⾏调⽤协程 async_crawler ,如下所示:

代码语言:python代码运行次数:0复制
if __name__ == "__main__":
 asyncio.run(async_crawler())

【小结】

文章基于Python语言详细介绍了多线程、协程和多进程并发编程。无论哪门编程语言,多线程和高并发都是技术进阶的必备知识之一。

我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

0 人点赞