@Author: Runsen
一说并发,你肯定想到了多线程+进程模型,确实,多线程+进程,正是解决并发问题的经典模型之一。但对于多核CPU,利用多进程+协程的方式,能充分利用CPU,获得极高的性能。协程也是实现并发编程的一种方式。
协程:是单线程下的并发,又称微线程。英文名是Coroutine。它和线程一样可以调度,但是不同的是线程的启动和调度需要通过操作系统来处理。
协程是一种比线程更加轻量级的存在,最重要的是,协程不被操作系统内核管理,协程是完全由程序控制的。
运行效率极高,协程的切换完全由程序控制,不像线程切换需要花费操作系统的开销,线程数量越多,协程的优势就越明显。
协程不需要多线程的锁机制,因为只有一个线程,不存在变量冲突。
对于多核CPU,利用多进程+协程的方式,能充分利用CPU,获得极高的性能。
注意协程这个概念完全是程序员自己想出来的东西,它对于操作系统来说根本不存在。操作系统只有进程和线程。
yield关键字相当于是暂停功能,程序运行到yield停止,send函数可以传参给生成器函数,参数赋值给yield。
def customer(): while True: number = yield print('开始消费:',number) custom = customer() next(custom) for i in range(5): print('开始生产:',i) custom.send(i)结果如下
开始生产: 0 开始消费: 0 开始生产: 1 开始消费: 1 开始生产: 2 开始消费: 2 开始生产: 3 开始消费: 3 开始生产: 4 开始消费: 4代码解析:
协程使用生成器函数定义:定义体中有 yield 关键字。yield 在表达式中使用;如果协程只需从客户custom接收数据,如果没有产出的值,那么产出的值是 None。首先要调用 next(…) 函数,因为生成器还没启动,没在 yield 语句处暂停,所以一开始无法发送数据。调用send方法,把值传给 yield 的变量,然后协程恢复,继续执行下面的代码,直到运行到下一个 yield 表达式,或者终止。async和await是原生协程,是Python3.5以后引入的两个关键词。
async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
下面,我们从一个demo示例看起,具体代码如下。
import time def print_num(num): print("Maoli is printing " + str(num) + " nows" ) time.sleep(1) print("Maoli prints" + str(num) + " OK") def main(nums): for num in nums: print_num(num) %time main([i for i in range(1,6)]) Maoli is printing 1 nows Maoli prints1 OK Maoli is printing 2 nows Maoli prints2 OK Maoli is printing 3 nows Maoli prints3 OK Maoli is printing 4 nows Maoli prints4 OK Maoli is printing 5 nows Maoli prints5 OK Wall time: 5 s%time 需要在jupyter notebook中运行,这是jupyter的语法糖。
上面代码是从上到下执行的。下面我们将上面的代码改成单线程协程版本。
注意py版本3.7以上,主要使用的是asyncio模块,如果出现AttributeError: module ‘asyncio‘ has no attribute ‘run‘报错,这是asyncio版本不兼容的原因,需要将Python版本提升至3.7以上。
import asyncio async def print_num(num): print("Maoli is printing " + str(num) + " nows" ) await asyncio.sleep(1) print("Maoli prints" + str(num) + " OK") async def main(nums): for num in nums: await print_num(num) %time asyncio.run(main([i for i in range(1,6)])) Maoli is printing 1 nows Maoli prints1 OK Maoli is printing 2 nows Maoli prints2 OK Maoli is printing 3 nows Maoli prints3 OK Maoli is printing 4 nows Maoli prints4 OK Maoli is printing 5 nows Maoli prints5 OK Wall time: 5.01 sasyncio.run() 函数用来运行最高层级的入口点 “main()” 函数。await 是同步调用等待一个协程。以下代码段会在等待 1 秒后打印 num,但在运行速度上没有发生改变。这里需要引入asyncio.create_task可等待对象才可以。
如果一个对象可以在 await 语句中使用,那么它就是可等待对象。
协程中的还一个重要概念,任务(Task)。
如果写一个数字是一个任务,那么毛利我要完成5个任务。
毛利我写个1-5都这么慢,不行,我要加速写。
asyncio.create_task() 函数用来并发运行作为 asyncio 任务 的多个协程。
import asyncio async def print_num(num): print("Maoli is printing " + str(num) + " nows" ) await asyncio.sleep(1) print("Maoli prints" + str(num) + " OK") async def main(nums): tasks = [asyncio.create_task(print_num(num)) for num in nums] for task in tasks: await task %time asyncio.run(main([i for i in range(1,6)])) Maoli is printing 1 nows Maoli is printing 2 nows Maoli is printing 3 nows Maoli is printing 4 nows Maoli is printing 5 nows Maoli prints1 OK Maoli prints3 OK Maoli prints5 OK Maoli prints2 OK Maoli prints4 OK Wall time: 1.01 s还可以写成await asyncio.gather(*tasks)这种方法
import asyncio async def print_num(num): print("Maoli is printing " + str(num) + " nows" ) await asyncio.sleep(1) print("Maoli prints" + str(num) + " OK") async def main(nums): tasks = [asyncio.create_task(print_num(num)) for num in nums] await asyncio.gather(*tasks) %time asyncio.run(main([i for i in range(1,6)]))*tasks 解包列表,将列表变成了函数的参数;与之对应的是, ** dict 将字典变成了函数的参数。
协程的写法简洁清晰,只要把 async / await 语法和 create_task 结合来用,就是Python中比较常见的协程写法。
今天也学到了很多东西呢,明天有什么新知识呢?真期待鸭如果喜欢文章可以关注我哦
https://docs.python.org/zh-cn/3/library/asyncio.html
本文已收录 GitHub,传送门~ ,里面更有大厂面试完整考点,欢迎 Star。
刘润森! 认证博客专家 Python Java 前端 17年就读于东莞XX学院化学工程与工艺专业,GitChat作者。Runsen的微信公众号是"Python之王",因为Python入了IT的坑,从此不能自拔。公众号内容涉及Python,Java计算机、杂谈。干货与情怀同在。喜欢的微信搜索:「Python之王」。个人微信号:RunsenLiu。不关注我公号一律拉黑!!!