协程
协程
单线程如何实现并发:
- 多道技术:切换 + 保存状态
# cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),
- 一种情况是该任务发生了阻塞
- 另一种情况是该任务计算的时间过长或有一个优先级更高的程序替代了它
# 第二种情况切换并不能提高效率,反而会降低效率。
# 基于之前的基础知识,可以使用yield,函数生成器来实现 单线程内程序间的切换和保存状态
- 单线程实现并发,需要在应用程序里控制多个任务的切换+保存状态
- 优点:应用程序级别速度要远远高于操作系统的切换
- 缺点:多个任务一旦有一个IO阻塞没有切,整个线程都阻塞在原地,该线程内的其他的任务都不能执行了
单线程下,我们不可避免程序中出现IO操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务,在一个任务遇到IO阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被CPU执行的状态,相当于我们在用户程序级别将自己的IO操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,IO比较少,从而更多的将CPU的执行权限分配给我们的线程。
协程是单线程下的并发,协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
-
python的线程属于内核级别的,即由操作系统控制调度,如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行
-
单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率。
-
协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级;单线程内就可以实现并发的效果,最大限度地利用cpu
-
协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程;
-
协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程。
greenlet模块
greenlet模块实现程序间的切换,缺点:遇到IO就阻塞
greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。
from greenlet import greenlet
def eat(name):
print('%s eat 1' %name)
g2.switch('egon')
print('%s eat 2' %name)
g2.switch()
def play(name):
print('%s play 1' %name)
g1.switch()
print('%s play 2' %name)
g1=greenlet(eat)
g2=greenlet(play)
g1.switch('egon') #可以在第一次switch时传入参数,以后都不需要
gevent模块
实现遇到gevent的IO自动切换
import gevent
def eat(name):
print('%s eat 1' %name)
gevent.sleep(2) # 注意使用的是gevent.sleep(),而不是 time.sleep()
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() # #者gevent.joinall([g1, g2])
g2.join()
print('主')
猴子补丁实现所有IO的自动切换
- from gevent import monkey;monkey.patch_all()放到文件的开头
- 这个一定要放在time.sleep 和socket这些io之前打补丁。
from gevent import monkey;monkey.patch_all()
import gevent
import time
def eat():
print('eat food 1')
time.sleep(2) # time.sleep()
print('eat food 2')
def play():
print('play 1')
time.sleep(1)
print('play 2')
g1=gevent.spawn(eat)
g2=gevent.spawn(play_phone)
gevent.joinall([g1, g2])
print('主')
协程:socket服务端并发
from gevent import spawn, monkey;monkey.patch_all()
import socket
def communication(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0: break
conn.send(data.upper())
except ConnectionResetError as e:
print(e)
break
conn.close()
def server(ip, port):
server = socket.socket()
server.bind((ip, port))
server.listen(5)
while True:
conn, addr = server.accept()
spawn(communication, conn) # 检测IO, communication
if __name__ == '__main__':
g1 = spawn(server, '127.0.0.1', 8080) # 检测IO,server
g1.join() # 等待g1运行结束,即一直在循环检测io