协程
进程:启动多个进程,进程之间是由操作系统负责调用
线程:启动多个线程,真正被CPU执行的最小单位。其他语言里面可以多个线程使用多个CPU,但是python不行。
开启一个线程,创建一个线程和关闭一个线程都需要浪费时间。
协程:
本质上是一个线程;其能够在多个任务之间切换来节省一些IO时间。协程中任务之间的切换也小号时间,但是开销要远远小于进程和线程之间切换的时间浪费
什么是在多个程序之间进行切换?
def consumer(): while 1:#这是一个生成器 x=yield print('处理了:',x) def producer(): c=consumer() next(c) for i in range(20): print('生产了数据:',i) c.send(i) producer() #利用prodcer函数最终控制了consumer函数,这就是两个程序之间来回的切换
上面中是利用yield实现了切换,但是实际上在协程中是利用greenlet来实现的,具体如下
from greenlet import greenlet def eat(): print('eating start')#这是第二步 g2.switch()#第三步 print('eating end')#第六步 g2.switch()#第七步 def play(): print('play start')#第四步 g1.switch()#第五步 print('play end')#第八步 g1=greenlet(eat) g2=greenlet(play) g1.switch()#这里切换到g1,这是第一步
注意协程的运作机理:当计算机在执行IO操作的时候,此时计算机的处理器暂时处于停顿状态,那么这对于计算机而言是资源浪费。利用协程能够将需要CPU处理的程序切换到这段空闲时间内,进而最大利用CPU 的执行效率,缩短程序执行的时间。
注意进程到线程再到协程,就是程序一步步的不断压缩CPU的暂停时间,只不过在进程和线程中是由操作系统来完成调度,而在协程中是由程序来完成而已。在协程中切换是由geenlet模块完成,最后使用的时候是使用Gevent模块,但是已经将greenlet模块封装在其中了。
一个4核CPU:
最多可以启动5个进程,
20个线程
每个线程最多可以开启500个协程
这样计算下来就是一个四核CPU最多可以开启5万个协程
greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。这就用到了Gevent模块。
import gevent import time def eat(): print('eating start') time.sleep(1) print('eating end') g2.switch() def play(): print('play start') time.sleep(1) print('play end') g1=gevent.spawn(eat) g2=gevent.spawn(play) g1.join() g2.join()
结果依然是
eating start
eating end
play start
play end
说明gvent基本上忽略了time.sleep()
import gevent import time def eat(): print('eating start') gevent.sleep(1) print('eating end') g2.switch() def play(): print('play start') gevent.sleep(1) print('play end') g1=gevent.spawn(eat) g2=gevent.spawn(play) g1.join() g2.join()
结果依然是
eating start
play start
eating end
play end
说明gevent.sleep起了作用
from gevent import monkey;monkey.patch_all()#将执行过程中的阻塞都打成一个包 import gevent import time import threading def eat(): print(threading.current_thread().getName())#输出当前线程的名字,结果是DummyThread-1,而之前线程的名字直接就是Thread-1,Dummy由虚假假冒的意思,所以表示这并不是一个真的线程 print('eating start') time.sleep(1) print('eating end') g2.switch() def play(): print(threading.current_thread().getName()) # 输出当前线程的名字 print('play start') time.sleep(1) print('play end') g1=gevent.spawn(eat) g2=gevent.spawn(play) g1.join() g2.join()
上例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 monkey;monkey.patch_all()#将执行过程中的阻塞都打成一个包 import gevent import time def task(): time.sleep(1) print('123') def sync():#同步 for i in range(5): task() def asyncs():#异步 g_list=[] for i in range(20): g=gevent.spawn(task) g_list.append(g) gevent.joinall(g_list)#等价于for g in g_list:g.join() sync() asyncs()
注意:只有当程序中有IO操作的时候使用协程才有价值,否则程序之间的切换也会造成效率的降低。所以一般在网络编程中使用协程比较多
爬虫:
import requests def get_url(url): res=requests.get(url) content=res.content.decode('utf-8') return len(content) res1=get_url('https://www.baidu.com/') print(res1)
在这个过程中,首先进程需要访问自己电脑的操作系统,一次IO操作
自己的操作系统分配一个端口通过网络协议访问百度,一次IO操作
百度服务器的操作系统访问到具体的内容,一次IO操作。
返回同样需要这三次IO操作,所以在整个过程中需要6次IO操作,那么就可以使用协程来提高效率
from gevent import monkey;monkey.patch_all() import gevent import requests def get_url(url): res=requests.get(url) content=res.content.decode('utf-8') return len(content) g1=gevent.spawn(get_url,'http://www.baidu.com/') g2=gevent.spawn(get_url,'http://www.sohu.com/') g3=gevent.spawn(get_url,'http://www.sina.com/') g4=gevent.spawn(get_url,'http://www.taobao.com/') g5=gevent.spawn(get_url,'http://www.ifeng.com/') gevent.joinall([g1,g2,g3,g4,g5]) print(g1.value) print(g2.value) print(g3.value) print(g4.value) print(g5.value)
通信
sever
from gevent import monkey;monkey.patch_all() import gevent import socket def func(): '''注意下面的这些代码就相当于具体干活的人''' conn.send(b'hello') ret = conn.recv(1024).decode('utf-8') print(ret) conn.close() sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() while 1: conn, addr = sk.accept()#这就相当于一个接线员,他只负责接线,把任务派发出去,那么下面func(),执行func函数就代表将接到的活派发出去,被执行了 g1=gevent.spawn(func)#,那么这里就可以使用协程 sk.close()
clent
import socket sk=socket.socket() sk.connect(('127.0.0.1',8080)) print(sk.recv(1024).decode('utf-8')) msg=input('>>>').encode('utf-8') sk.send(msg) sk.close()