协程
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。(cpu不知道,是用户自己控制的)
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈(线程的上下文切换是保存在CPU,协程就不是)。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置(yield生产消费就是协程)。
协程的好处:
无需线程上下文切换的开销
无需原子操作锁定及同步的开销
"原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
linux第三方库安装greenlet gevent
pip3 install gevent
pip3 install greenlet
greenlet(已经封装好的协程的模块(手动切换协程)
gevent(已经封装好的协程的模块(自动切换协程))
试验greenlet
__author__ = "Burgess Zheng"
from greenlet import greenlet#greenlet 已经封装好的协程
def test1():
print(12)# 1.首先打印显示:12
gr2.switch()#切换(启动)gr2该协程
print(34)#3.打印显示:34
gr2.switch()#切换(启动)gr2该协程
def test2():
print(56)#2.打印显示:56
gr1.switch()#切换(启动)gr1该协程
print(78)#4.打印显示:78
gr1 = greenlet(test1) #实例化一个协程
gr2 = greenlet(test2) #实例化一个携程
gr1.switch()#切换(启动)gr1该协程 (现在开始执行test1协程)
执行结果:
试验Gevent
__author__ = "Burgess Zheng"
import gevent
def foo():
print('Running in foo') # 1
gevent.sleep(2)
print('Explicit context switch to foo again')#6
def bar():
print('Explicit精确的 context内容 to bar') #2
gevent.sleep(1)
print('Implicit context switch back to bar')#5
def func3():
print("running func3 ") #3
gevent.sleep(0)
print("running func3 again ")#4
gevent.joinall([ #一次性生成多个协程
gevent.spawn(foo), #实例化生成一个自动协程
gevent.spawn(bar), #实例化生成一个自动协程
gevent.spawn(func3), #实例化生成一个自动协程
])
执行结果:
试验协程并发爬网页 和 列表for调用同步串行爬网页的区别和效率
__author__ = "Burgess Zheng"
from urllib import request#简单爬虫
import gevent,time
from gevent import monkey#因为gevent不知道urllib内部是否在进行IO操作,
# 所以需要我们需要让gevent知道urllib在进程内部操作,就需要引入monkey
monkey.patch_all() #把当前程序的所有的io操作给我单独的做上标记
def f(url):
print('GET: %s' % url)
resp = request.urlopen(url)#获取该网址页面的内容
data = resp.read()
#f = open("url.html","wb")#创建文件url.html
#f.write(data)#获得网址内容写入文件
#f.close()#完成以后关闭
print('%d bytes received from %s.' % (len(data), url))
#使用列表循环操作爬虫(同步串行)
urls = ['https://www.python.org/',
'https://www.yahoo.com/',
'https://github.com/' ]
time_start = time.time()#开始执行时间
for url in urls:
f(url)
print("同步cost",time.time() - time_start)#当前时间减去开始执行时间=获得执行时间
#使用协程爬虫(效果是异步并行)
async_time_start = time.time()#异步开始执行时间
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
print("异步cost",time.time() - async_time_start)
#当前时间减去异步开始执行时间=获得执行时间
执行结果:
socket协程高并发处理试验(比Python自带的socketserver(线程)还NB)
socketserver
__author__ = "Burgess Zheng"
import sys
import socket
import time
import gevent
from gevent import socket, monkey
monkey.patch_all()
def server(port):
s = socket.socket()#启动socket
s.bind(('0.0.0.0', port))#绑定端口
s.listen(500)#允许500个并发
while True:
cli, addr = s.accept()#接收到数据以后会返回2个值:链接标记位和对方的地址
gevent.spawn(handle_request, cli)#启动线程handle_request 链接标记位作为实参
#这样每进来一个用户访问就启动一个协程,就代表可以处理高并发
def handle_request(conn):
try:
while True:
data = conn.recv(1024)#接收数据
print("recv:", data)#打印接收的数据
conn.send(data)#返回数据
if not data:#如果数据是空
conn.shutdown(socket.SHUT_WR)#关闭客户端 ,用break也可以
except Exception as ex:#抓异常
print(ex)#打印异常
finally:#无论是否异常继续执行下面的
conn.close()#关闭连接
if __name__ == '__main__':
server(8001)
socketclient
__author__ = "Burgess Zheng"
import socket
HOST = 'localhost' # The remote host
PORT = 8001 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
msg = bytes(input(">>:"), encoding="utf8")
s.sendall(msg)
data = s.recv(1024)
#
print('Received', repr(data))#repr格式输出 也可以直接data ,没鸟用
s.close()
执行结果: