十四、协程
协程
协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
'''
进程:资源单位
线程:执行单位
协程:这个概念完全是程序员自己意淫出来的,根本不存在
协程思想:单线程下实现并发
程序员自己在代码层面上监测所有的IO操作
一旦遇到IO操作,我们就在代码层面完成切换
这样给CPU的感觉是你这个程序一致在运行,没有IO
从而提升程序的运行效率
如何做到切换+保存状态?
切换:
遇到IO就切,提升效率
保存状态
保存上一次执行的状态,下一次接着上一次的操作继续往后执行,使用yield关键字
'''
需要注意的是:
#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
#2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
优点如下:
#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
#2. 单线程内就可以实现并发的效果,最大限度地利用cpu
缺点如下:
#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
#2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
总结协程特点:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈
- 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
一、gevent模块(了解)
注意:
'''
gevent模块本身无法监测常见的一些io操作
在使用的时候需要额外的导入一句话(monkey补丁):
from gevent import monkey
monkey.patch_all()
又由于上面的两句指令,在使用gevent模块的时候是必须导入的,所以支持如下简写:
from gevent import monkey;monkey.patch_all()
'''
1、安装gevent模块
pip3 intsall gevent
2、spawn()
1.spawn()方法是一个异步方法,即使用该方法的时候,主线程依然在运行
2.spawn()的返回值是一个Greenlet类,该类中存在join方法,可以阻塞主线程,等待其他子线程执行结束
- 语法:
from gevent import spawn
from gevent import monkey;monkey.patch_all()
'''
gevent模块本身无法监测常见的一些io操作
在使用的时候需要额外的导入一句话(monkey补丁):
from gevent import monkey
monkey.patch_all()
又由于上面的两句指令,在使用gevent模块的时候是必须导入的,所以支持如下简写:
from gevent import monkey;monkey.patch_all()
'''
g = spawn(fn, *args)
'''
参数介绍:
fn:填入想要被监测IO操作的函数名,一旦该函数内执行了IO操作,则立马切换CPU资源
*args:即填入fn函数需要的参数
返回值:
spawn的返回值是一个Greenlet类,可以调用该类中的join方法,实现阻塞主线程,等待其他子线程执行结束
<class 'gevent._gevent_cgreenlet.Greenlet'>
'''
二、greenlet模块
greenlet模块并没有自动检测IO操作的功能,它一般是配合yield实现IO检测,这种方法效率极低
- 语法:
from greenlet import greenlet
g = greelet(fn)
'''
参数介绍:
fn:表示需要进行切换操作的函数名,即与该函数绑定
'''
1、安装greenlet模块
pip3 install greenlet
2、switch()
调用switch()方法,则进行CPU资源切换
- 语法:
from greenlet import greenlet
g = greelet(fn)
g.switch(obj)
'''
参数介绍:
obj:传入给g变量所绑定的fn函数参数
可以在第一次switch时传入参数,以后默认值则为第一次switch传入的参数
'''
三、协程实现TCP服务端并发
服务器
from gevent import spawn
from gevent import monkey;monkey.patch_all()
import socket
# 通信循环
def talk(a):
while True:
try:
client_data = a.recv(1024).decode('utf8')
if len(client_data) == 0:
break
print(client_data)
a.send(client_data.upper().encode('utf8'))
except ConnectionError as e:
print(e)
break
a.close()
def server(ip, port):
s = socket.socket()
s.bind((ip, port))
s.listen(128)
while True:
a, b = s.accept()
spawn(talk, a)
if __name__ == '__main__':
g1 = spawn(server, '127.0.0.1', 8000)
g1.join()

浙公网安备 33010602011771号