协程
- 进程:资源单位
- 线程:执行单位
- 协程:单线程下实现并发(能够在多个任务之间切换和保存状态来节省IO),这里注意区分操作系统的切换+保存状态是针对多个线程而言,而我们现在是想在单个线程下自己手动实现操作系统的切换+保存状态的功能
注意协程这个概念完全是程序员自己想出来的东西,它对于操作系统来说根本不存在。操作系统只知道进程和线程。并且需要注意的是并不是单个线程下实现切换+保存状态就能提升效率,因为你可能是没有遇到io也切,那反而会降低效率
再回过头来想上面的socket服务端实现并发的例子,单个线程服务端在建立连接的时候无法去干通信的活,在干通信的时候也无法去干连接的活。这两者肯定都会有IO,如果能够实现通信io了我就去干建连接,建连接io了我就去干通信,那其实我们就可以实现单线程下实现并发
将单个线程的效率提升到最高,多进程下开多线程,多线程下用协程>>> 实现高并发!!!
三者都是实现并发的手段
#串行执行
import time
def func1():
for i in range(10000000):
i+1
def func2():
for i in range(10000000):
i+1
start = time.time()
func1()
func2()
stop = time.time()
print(stop - start)
#基于yield并发执行
import time
def func1():
while True:
10000000+1
yield
def func2():
g=func1()
for i in range(10000000):
# time.sleep(100) # 模拟IO,yield并不会捕捉到并自动切换
i+1
next(g)
start=time.time()
func2()
stop=time.time()
print(stop-start)
我们可以通过yield能够实现保存上次运行状态,但是无法识别遇到io才切换。
gevent模块
一个spawn就是一个帮你管理任务的对象
gevent模块不能识别它本身以外的所有的IO行为,但是它内部封装了一个模块,能够帮助我们识别所有的IO行为
from gevent import monkey;monkey.patch_all() 检测所有的IO行为
from gevent import spawn,joinall joinall列表里面放多个对象,实现join效果
import time
def play(name):
print('%s play 1' %name)
time.sleep(5)
print('%s play 2' %name)
def eat(name):
print('%s eat 1' %name)
time.sleep(3)
print('%s eat 2' %name)
start=time.time()
g1=spawn(play,'dog')
g2=spawn(eat,'cat')
# g1.join()
# g2.join()
joinall([g1,g2])
print('主',time.time()-start) # 单线程下实现并发,提升效率
协程实现服务端客户端通信
链接和通信都是io密集型操作,我们只需要在这两者之间来回切换其实就能实现并发的效果
服务端监测链接和通信任务,客户端起多线程同时链接服务端
服务端
from gevent import monkey;monkey.patch_all()
from socket import *
from gevent import spawn
def communicate(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0: break
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
def server(ip, port, backlog=5):
server = socket(AF_INET, SOCK_STREAM)
server.bind((ip, port))
server.listen(backlog)
while True: # 链接循环
conn, client_addr = server.accept()
print(client_addr)
# 通信
spawn(comunicate,conn)
if __name__ == '__main__':
g1=spawn(server,'127.0.0.1',8080)
g1.join()
客户端
扫描二维码关注公众号,回复:
6224058 查看本文章
from threading import Thread, current_thread
from socket import *
def client():
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
n = 0
while True:
msg = '%s say hello %s' % (current_thread().name, n)
n += 1
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
if __name__ == '__main__':
for i in range(500):
t = Thread(target=client)
t.start()
原本服务端需要开启500个线程才能跟500个客户端通信,现在只需要一个线程就可以扛住500客户端
进程下面开多个线程,线程下面再开多个协程,最大化提升软件运行效率
IO模型
- 阻塞IO
场景描述
我和女友点完餐后,不知道什么时候能做好,只好坐在餐厅里面等,直到做好,然后吃完才离开。
女友本想还和我一起逛街的,但是不知道饭能什么时候做好,只好和我一起在餐厅等,
而不能去逛街,直到吃完饭才能去逛街,中间等待做饭的时间浪费掉了。这就是典型的阻塞。
- 非阻塞IO
场景描述
我女友不甘心白白在这等,又想去逛商场,又担心饭好了。
所以我们逛一会,回来询问服务员饭好了没有,来来回回好多次,饭都还没吃都快累死了啦。
这就是非阻塞。需要不断的询问,是否准备好了。
- IO多路复用
场景描述
与第二个方案差不多,餐厅安装了电子屏幕用来显示点餐的状态,这样我和女友逛街一会,
回来就不用去询问服务员了,直接看电子屏幕就可以了。
这样每个人的餐是否好了,都直接看电子屏幕就可以了,这就是典型的IO多路复用。
- 异步IO
场景描述
女友不想逛街,又餐厅太吵了,回家好好休息一下。于是我们叫外卖,打个电话点餐,
然后我和女友可以在家好好休息一下,饭好了送货员送到家里来。
这就是典型的异步,只需要打个电话说一下,然后可以做自己的事情,饭好了就送来了。