python 协程详解教程
一.协程的概念
协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。
一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制):
一种情况是该任务发生了阻塞;
另外一种情况是该任务计算的时间过长或有一个优先级更高的程序替代了它。
协程本质上就是一个线程,以前线程任务的切换是由操作系统控制的,遇到I/O自动切换,现在我们用协程的目的就是较少操作系统切换的开销(开关线程,创建寄存器、堆栈等,在他们之间进行切换等),在我们自己的程序里面来控制任务的切换。
协程就是告诉Cpython解释器,你不是nb吗,不是搞了个GIL锁吗,那好,我就自己搞成一个线程让你去执行,省去你切换线程的时间,我自己切换比你切换要快很多,避免了很多的开销。
对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:
1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换
二.实现协程的方式
1.greenlet实现协程☆☆☆
greenlet是古老的协程实现方式
pip3 install greenlet
from greenlet import greenletdef eat(name):print('%s eat 1' % name) # 2g2.switch('taibai') # 3print('%s eat 2' % name) # 6g2.switch() # 7def play(name):print('%s play 1' % name) # 4g1.switch() # 5print('%s play 2' % name) # 8g1 = greenlet(eat)
g2 = greenlet(play)g1.switch('taibai') # 可以在第一次switch时传入参数,以后都不需要 #1
2.yield☆
是一种协程,然而并没有什么卵用
def func1():yield 1yield from func2yield 2def func2():yield 3yield 4
f1 = func1()
for item in f1:print(item)
"""
1
3
4
2
"""
3.Gevent模块☆☆☆☆(直接看4也可)
Gevent是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet,它是以C扩展模块形式接入Python的轻量级协程。Greenlet全部运行在主程序操作系统进程的内部,但他们被协作式地调度。
i.安装
pip3 install gevent
ii.用法
g1=gevent.spawn(func,1,2,3,x=4,y=5)
# 创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的,spawn是异步提交任务g2=gevent.spawn(func2)g1.join() #等待g1结束g2.join() #等待g2结束 有人测试的时候会发现,不写第二个join也能执行g2,是的,协程帮你切换执行了,但是你会发现,如果g2里面的任务执行的时间长,但是不写join的话,就不会执行完等到g2剩下的任务了#或者上述两步合作一步:
gevent.joinall([g1,g2])g1.value #拿到func1的返回值
遇到IO阻塞时会自动切换任务
import geventdef eat(name):print('%s eat 1' % name)gevent.sleep(2)print('%s eat 2' % name)def play(name):print('%s play 1' % name)gevent.sleep(1)print('%s play 2' % name)g1 = gevent.spawn(eat, 'egon')
g2 = gevent.spawn(play, name='egon')
g1.join()
g2.join()
# 或者gevent.joinall([g1,g2])
print('主')
上例gevent.sleep(2)
模拟的是gevent可以识别的io阻塞;
而time.sleep(2)
或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了
from gevent import monkey;
monkey.patch_all() #必须放到被打补丁者的前面,如time,socket模块之前
或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()
放到文件的开头:
from gevent import monkeymonkey.patch_all() # 必须写在最上面,这句话后面的所有阻塞全部能够识别了import gevent # 直接导入即可
import timedef eat():# print() print('eat food 1')time.sleep(2) # 加上monkey就能够识别到time模块的sleep了print('eat food 2')def play():print('play 1')time.sleep(1) # 来回切换,直到一个I/O的时间结束,这里都是我们个gevent做得,不再是控制不了的操作系统了。print('play 2')g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
gevent.joinall([g1, g2])
print('主')
我们可以用threading.current_thread().getName()
来查看每个g1和g2,查看的结果为DummyThread-n
,即假线程,虚拟线程,其实都在一个线程里面
进程线程的任务切换是由操作系统自行切换的,你自己不能控制
协程是通过自己的程序(代码)来进行切换的,自己能够控制,只有遇到协程模块能够识别的IO操作的时候,程序才会进行任务切换,实现并发效果,如果所有程序都没有IO操作,那么就基本属于串行执行了。
iii.总结
这是一个吊炸天神器,但宝刀老矣,官方已出内置的协程工具,这种方法即将被asyncio淘汰
4.asyncio☆☆☆☆☆☆
在pyhon3.4的时候推出的,内置模块,不用安装,确保你的Python解释器版本大于3.4
i.直接上代码:
import asyncio@asyncio.coroutine #表示这不再是一个普通函数,已经升级为可以异步的战斗机了!
def func1():print(1)yield from asyncio.sleep(2) #模拟io,生产中换成实际的ioprint(2)@asyncio.coroutine
def func2():print(3)yield from asyncio.sleep(2)print(4)#把任务放进任务池中
tasks = [asyncio.ensure_future(func1()),asyncio.ensure_future(func2()),
]loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
ii.async & await关键字
Python3.5才出现,保证你的版本符合要求
本质上和上面用法是一样的,只是替换掉关键字而已
import asyncioasync def func1(): #async替换掉关键字print(1)await asyncio.sleep(2) #等待ioprint(2)async def func2():print(3)await asyncio.sleep(2) print(4)tasks = [asyncio.ensure_future(func1()),#future对象较为底层,是task的基类asyncio.ensure_future(func2()),
]loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
iii.run方法,task对象
Python3.7才出现,保证你的版本符合要求
run方法包含了
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
最常用的协程方法
import asyncioasync def func1(): # async替换掉关键字print(1)await asyncio.sleep(2) # 等待ioprint(2)return "func1"async def func2():print(3)await asyncio.sleep(2)print(4)return "func2"async def main():tasks = [asyncio.create_task(func1()), # 必须声名在在异步函数中,因为放在外面没有task对象会报错asyncio.create_task(func2()),]done, pending = await asyncio.wait(tasks, timeout=None) # done:返回结果,pending:返回未完成的协程函数for i in done:print(i.result())print(pending)# 运行
asyncio.run(main())
若想把tasks对象放在外面,需要修改代码
import asyncioasync def func1(): # async替换掉关键字print(1)await asyncio.sleep(2) # 等待ioprint(2)return "func1"async def func2():print(3)await asyncio.sleep(2)print(4)return "func2"tasks = [func1(),#不能使用task对象,因为还没有创建事件循环loop对象func2(),
]
#wait方法自动把协程函数创建task对象
done, pending = asyncio.run(asyncio.wait(tasks, timeout=None)) # done:返回结果,pending:返回未完成的协程函数
for i in done:print(i.result())
print(pending)
iv.有些库不支持asyncio语法,如requests
当我们拿着asyncio模块实行异步爬虫的时候
import asyncio
import requestsurls = ["http://www.smilenow.top","http://www.baidu.com","http://www.163.com"
]async def get_cont(url):print("准备下载:", url)htm = requests.get(url=url).textprint(url, "已经下载完毕")return urltasks = map(lambda x: get_cont(x), urls)asyncio.run(asyncio.wait(tasks, timeout=None)) # done:返回结果,pending:返回未完成的协程函数
结果
准备下载: http://www.baidu.com
http://www.baidu.com 已经下载完毕
准备下载: http://www.163.com
http://www.163.com 已经下载完毕
准备下载: http://www.smilenow.top
http://www.smilenow.top 已经下载完毕
什么鬼,根本没有实现异步好吗?怎么办?用线程池替代!
import asyncio
import requestsurls = ["http://www.smilenow.top","http://www.baidu.com","http://www.163.com"
]async def get_cont(url):print("准备下载:", url)loop = asyncio.get_event_loop()future = loop.run_in_executor(None,requests.get,url)#变成多线程方式运行了await futureprint(url, "已经下载完毕")return urltasks = map(lambda x: get_cont(x), urls)asyncio.run(asyncio.wait(tasks, timeout=None)) # done:返回结果,pending:返回未完成的协程函数
v.asyncio 异步操作redis
下载支持异步的redis模块
pip install aioredis
import aioredis
import asyncioclass Redis:_redis = Noneasync def get_redis_pool(self, *args, **kwargs):if not self._redis:self._redis = await aioredis.create_redis_pool(*args, **kwargs)return self._redisasync def close(self):if self._redis:self._redis.close()await self._redis.wait_closed()async def get_value(key):redis = Redis()r = await redis.get_redis_pool(('127.0.0.1', 6379), db=7, encoding='utf-8')value = await r.get(key)print(f'{key!r}: {value!r}')await redis.close() if __name__ == '__main__':asyncio.run(get_value('key')) # need python3.7
vi.aiomysql异步操作mysql
安装:
pip install aiomysql
import asyncio
import aiomysqlasync def execute():conn = await aiomysql.connect(host='localhost', port=3306, user="root", password='123', db='my')cur = await conn.cursor()await cur.excute("select * from user")result = await cur.fetchall()print(result)await cur.close()await conn.close()asyncio.run(execute())
v.不够快,uvloop让速度飞翔!!!
uvloop 使得 asyncio 更快. 实际上,比nodejs,gevent,以及其他任何Python异步框架至少快两倍 .uvloop asyncio 基于性能的测试接近于Go程序.
这是一个被各大框架青睐的模块
安装:
pip install uvloop
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())#下面正常书写asyncio代码
python 协程详解教程相关推荐
- python协程详解
目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...
- python协程详解_python协程详解
原博文 2019-10-25 10:07 − # python协程详解 ![python协程详解](https://pic2.zhimg.com/50/v2-9f3e2152b616e89fbad86 ...
- python协程详解_彻底搞懂python协程-第一篇(关键词1-4)
任何复杂的概念或系统都不是凭空出现的,我们完全可以找到它的演化历程,寻根究底终会发现,其都是在一系列并不那么复杂的简单组件上发展演化而来! by 落花僧 本文通过一系列关键概念,逐步递进理解协程. 0 ...
- python协程详解_对Python协程之异步同步的区别详解
一下代码通过协程.多线程.多进程的方式,运行代码展示异步与同步的区别. import gevent import threading import multiprocessing # 这里展示同步和异 ...
- Python 协程详解
目录 什么是协程 Python 对协程的支持经历了多个版本: 一.协程实现方法: 1.greenlet,早期模块 2.yield关键字(Python2.x开始) 3.asyncio装饰器(Python ...
- Python进程、线程、协程详解
进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配.任务的调度. ...
- 【转载】Python线程、进程和协程详解
从操作系统角度 操作系统处理任务,调度单位是进程和线程. 进程:表示一个程序的执行活动(打开程序.读写程序数据.关闭程序) 线程:执行某个程序时,该进程调度的最小执行单位(执行功能1,执行功能2) 一 ...
- python多核cpu_Python中的多核CPU共享数据之协程详解
一 : 科普一分钟 尽管进程间是独立存在的,不能相互访问彼此的数据,但是在python中却存在进程间的通信方法,来帮助我们可以利用多核CPU也能共享数据. 对于多线程其实也是存在一些缺点的,不是任何场 ...
- 线程和协程详解-python
1.前言 关于基本概念部分这里不再详述,可以参考之前的文章或者自行查阅相关文章. 由于python中线程的创建.属性和方法和进程很相似,这里也不再讲解. 这里重点讲解下多线程访问共享数据的相关问题. ...
最新文章
- map和foreach的区别和应用场景_支付宝小程序和微信小程序,两者有何区别?
- (C++)1002 A+B for Polynomials
- Latex中的列表环境[一]
- 中国工商银行已使用OceanBase!
- UNITY插件信息收集
- 让你的C程序更有效率的10种方法
- 使用Mali Graphics Debugger调优Unity程序(Killer示例)
- SQL Server2008官方下载地址
- 烽火狼烟丨Microsoft多个安全漏洞风险提示
- 使用IDEA格式化JSON数据串
- 华为服务器通过ilo虚拟光驱,如何通过ilo开启服务器远程桌面
- arXiv 注册完整过程(图文详解)
- Unity 多点触控 禁用与启用
- 圈圈教你玩USB学习总结
- 【reverse】通俗易懂的gcc内联汇编入门+示例:实现花指令
- Linux CentOS7 VMware LAMP架构Apache用户认证、域名跳转、Apache访问日志
- Karaf-cellar 集群配置
- Debian之安装完成后找不到命令解决办法
- python爬取沪深所有股票数据并生成Excel文件
- 环海陆港今日财经报告