首页 技术 正文
技术 2022年11月8日
0 收藏 820 点赞 2,025 浏览 2389 个字

8.8 协程

​我们都知道线程间的任务切换是由操作系统来控制的,而协程的出现,就是为了减少操作系统的开销,由协程来自己控制任务的切换

​协程本质上就是线程。既然能够切换任务,所以线程有两个最基本的功能:一是保存状态;二是任务切换

8.8.1 协程的特点

【优点】

  • 线程任务切换开销小,属于程序级的切换,操作系统感知不到
  • 单线程内就可以实现并发的效果,最大限度的利用CPU

【缺点】

  • 协程的本质是单线程,无法利用多核;可以一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
  • 协程是单个线程执行多个任务,一旦协程遇到阻塞,将会阻塞整个线程

【特点】

  • 必须要在单线程中实现并发
  • 修改共享数据不需要加锁
  • 用户程序内保存多个控制流的上下文栈
  • 一个协程遇到IO操作自动切换到其它协程

8.8.2 Greenlet

使用grennlet第三方库实现任务间的切换

from greenlet import greenletdef get_money(name):
print(f"{name} get 10 $")
g2.switch('jiawen')
print(f"{name} get 20 $")
g2.switch()def buy_goods(name):
print(f"{name} buy no.1 good ")
g1.switch()
print(f"{name} buy no.2 good ")
g1.switch()if __name__ == '__main__':
g1 = greenlet(get_money)
g2 = greenlet(buy_goods) g1.switch('gailun') # switch在第一次时必须要传入参数,以后就不需要了

效率对比

from greenlet import greenlet
import timedef f1():
re = 1
for i in range(10000000):
re *= i
g2.switch()def f2():
re = 1
for i in range(10000000):
re += i
g1.switch()if __name__ == '__main__':
start = time.time()
g1 = greenlet(f1)
g2 = greenlet(f2) g1.switch() # switch在第一次时必须要传入参数,以后就不需要了
print(f'{time.time()- start}') # 5.822627305984497import timedef f1():
re = 1
for i in range(10000000):
re *= idef f2():
re = 1
for i in range(10000000):
re += istart = time.time()
f1()
f2()
print(f'{time.time()- start}') # 1.041489601135254

【结论】单纯的切换,在没有IO阻塞的情况下,协程的效率反而降低

8.8.3 Gevent介绍

Gevent也是一个第三方库,主要用来实现并发同步或是异步编程,在gevent中用到的主要模式是Greenlet, Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

【方法】

​gevent.spawn(func,*args,**kwargs) spawn括号内第一个参数是函数名,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数func,spawn是异步提交任务

​join() 等待调用者结束

​value() 拿到调用者的返回值

遇到IO阻塞会自动切换

import geventdef get_money(name):
print(f"{name} get 10 $")
gevent.sleep(2) #模拟的是gevent可以识别的IO阻塞
print(f"{name} get 20 $")def buy_goods(name):
print(f"{name} buy no.1 good ")
gevent.sleep(1)
print(f"{name} buy no.2 good ")if __name__ == '__main__':
g1 = gevent.spawn(get_money,'gailun')
g2 = gevent.spawn(buy_goods,name='jiawen')
g1.join()
g2.join()
# gevent.joinall([g1,g2])
print('__main__')
# 输出
gailun get 10 $
jiawen buy no.1 good
jiawen buy no.2 good
gailun get 20 $
__main__

【注意】如果要使gevent识别所有的io阻塞,放到被打补丁者的前面或者直接写在在文件的最开头写上以下代码

rom gevent import monkey;monkey.patch_all()

应用

# 爬虫
from gevent import monkey;monkey.patch_all()
import gevent
import requests
import timedef get_inf(url):
print(f'GET:{url}')
res = requests.get(url)
if res.status_code == 200:
print(f"{len(res.text)} get from {url}")if __name__ == '__main__':
start_time = time.time()
gevent.joinall(
[gevent.spawn(get_inf,'https://www.python.org/'),
gevent.spawn(get_inf,'https://www.yahoo.com/'),
gevent.spawn(get_inf,'https://github.com/'),
]
)
print(f"take {time.time()-start_time} secondes")
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,120
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,592
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,437
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,208
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,844
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,929