python 中协程
异步非阻塞(I/O多路复用)
- I/O是指Input/Output(输入/输出), I/O多路复用是用于提升效率,单个进程通过一种机制可以同时监听多个网络连接IO,即监视多个文件描述符,一旦描述符就绪(读就绪和写就绪),能通知程序进行相应的读写操作。如进行多个网络请求时,可以通过非阻塞将请求全部发出,然后监听每个请求,如果发生变化,就直接进行下一步操作
- 常用的监听函数是select, 监听模式有select,和poll,以及epoll, r, w, e = select.select( rlist, wlist, errlist [,timeout] ),rlist,wlist和errlist均是文件描述符,就是一个整数,或者一个拥有返回文件描述符的函数fileno()的对象。rlist: 等待读就绪的文件描述符数组,wlist: 等待写就绪的文件描述符数组errlist: 等待异常的数组
协程(greenlet和gevent)
- 协程模块指的就是greenlet,但是greenlet模块实现不了单线程并发,只能通过greenlet+I/O切换实现单线程并发,gevent模块封装了此方法.
- 协程+I/O切换,只有遇到I/O密集型操作时才能实现单线程并发
- 协程与线程和进程不同,在计算机中是不存了的,是程序员创造出来的东西(微线程),用来实现执行过程中的切换,模块greenlet
import greenlet
def fun1():
print(1)
gr2.switch()
print(2)
gr2.switch()
def fun2():
print('a')
gr1.switch()
print('b')
gr1 = greenlet.greenlet(fun1)
gr2 = greenlet.greenlet(fun2)
gr1.switch()
- 单纯的协程是无用的,反而会影响执行效率,所以需要搭配异步非阻塞(I/O),遇到I/O就进行切换,这样才能实现单线程的并发
- greenlet+I/O多路复用,就是gevent实现遇到I/O自动执行切换,实现单线程并发,代码如下:
from gevent import monkey
monkey.patch_all()
import requests,time
import gevent
start = time.time()
def GetHtml(url):
'''
获取源码的封装
:param url: 传入url链接
:return: 返回该连接对应源码
'''
try:
address = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36',
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"
}
r = requests.get(url,headers = address)
r.encoding = r.apparent_encoding
r.raise_for_status()
html = r.text
print(url,'\n',)
except Exception as e:
print(e)
gevent.joinall([
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=a"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=b"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=c"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=d"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=e"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=f"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=g"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=h"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=i"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=j"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=k"),
gevent.spawn(GetHtml,"https://www.baidu.com/s?wd=l"),
])
end = time.time()
print('运行时间%.2f:' %(end-start))
线程,进程,协程的区别
- 进程,资源分配的最小单元,操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源)
- 线程,工作的最小单元,是操作系统调度(CPU调度)执行的最小单位
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程;
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源;
- 协程,人为定义的微线程协程,是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。
- 协程具有极高的执行效率,由于在一个线程中,不需要考虑变量,因为不存在同时写变量冲突,协程中也不需要考虑锁机制
- 常用的实现协程的模块gevent,以及基于事件循环的框架Twisted