协程
又称微线程,纤程。英文名Coroutine。
协程是一种用户态的轻量级线程
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存在其他地方,在切回来的时候,
恢复先前保存的寄存器上下文和栈。因此,协程能够保存上一次调用时的状态(即所有局部状态的一个特定的组合)
每次过程重入时,就相当于进入上一次调用的状态
换种说法:进入上一次离开时所处逻辑流的位置。
协程的好处:
无需线程上下文切换的开销
无需原子操作锁定及同步的开销
方便切换控制流,简化编程模型
高并发,高扩展性,低成本:一个CPU支持上万个协程不是问题
缺点:
无法利用多核资源:协程的本质是个单线程,他不能同时将单个CPU的多个核用上,
协程需要和进程配合才能运行在多CPU上
进行阻塞操作会阻塞掉整个程序
- yield版协程
# Author : Xuefeng
import time
import queue
def consumer(name):
'''
定义消费者
:param name:
:return:
'''
print("----start eating meat--------")
while True:
# 循环生产肉
new_meat = yield
print("%s is eating meat %s" %(name, new_meat))
time.sleep(1)
def producer():
'''
定义消费者
:return:
'''
# 触发yield
r = con.__next__()
r = con2.__next__()
n = 0
while n<5:
n+=1
con.send(n)
con2.send(n)
print("\033[41;1m [producer] \033[0m is making meat %s" %n)
if __name__=="__main__":
con = consumer("c1")
con2 = consumer("c2")
p = producer()
- greenlet版协程 手动切换
# Author : Xuefeng
# 手动切换
from greenlet import greenlet
def test1():
print(12)
# 跳转下一个协程
gr2.switch()
# 跳回
print(34)
# 跳转
gr2.switch()
def test2():
print(56)
# 跳转
gr1.switch()
# 跳回
print(78)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
- gevent版协程 手动切换
# Author : Xuefeng
# 自动切换
import gevent
def foo():
print("Running in foo")
gevent.sleep(3)
print("Explicit context switch to foo again")
def bar():
print("Explicit switch to the bar")
gevent.sleep(1)
print("Implicit context in the bar")
def func():
print("Explicit switch to the func")
gevent.sleep(2)
print("Implicit context in the func")
# 设置将要自动切换的协程
gevent.joinall([
gevent.spawn(foo),
gevent.spawn(bar),
gevent.spawn(func)
])
- 利用协程实现简单的爬虫
# Author : Xuefeng
from urllib import request
import gevent
from gevent import monkey
import time
# 对所有io操作进行标记,相当于gevent.sleep,实现协程并发操作
monkey.patch_all()
def reptile(url):
'''
Define the function for reptile the website
:param url: The address of the website
:return:
'''
print("Reptile the %s" %url)
info = request.urlopen(url)
data = info.read()
print(data)
print("%d bytes has been get from %s" %(len(data), url))
# print("\n")
# 串行执行
print("-----------one by one run--------")
start_time = time.time()
urls = [
"http://www.baidu.com/",
"http://www.sogou.com/",
"http://www.meitu.com/"
]
for url in urls:
reptile(url)
print("The one by one run cost %s time" %(time.time()-start_time))
print("\n")
# 并发执行
print("------------async_run-----------")
async_start = time.time()
gevent.joinall([
gevent.spawn(reptile, "http://www.baidu.com/"),
gevent.spawn(reptile, "http://www.sogou.com/"),
gevent.spawn(reptile, "http://www.meitu.com/")
])
print("The async run cost %s time" %(time.time()-async_start))
- 用协程创建服务器与客户交互
1) 服务器端
# Author : Xuefeng
import socket
import gevent
from gevent import monkey
# 对所有协程进行自动标记,帮助实现自动切换
monkey.patch_all()
def server(port):
'''
定义服务器端,设置链接,监听,等待并接收请求
:param port: 端口号
:return:
'''
s = socket.socket()
s.bind(('0.0.0.0', port))
s.listen(500)
while True:
cli, addr = s.accept()
gevent.spawn(handle_request, cli)
def handle_request(conn):
'''
定义处理请求函数,接收并发送数据
:param conn: 建立的链接
:return:
'''
try:
while True:
# 接收数据
data = conn.recv(1024)
print("Recv:", data)
# 发送数据
conn.send(data)
# 没有数据来到,断开链接
if not data:
conn.shutdown(socket.SHUT_WR)
except Exception as e:
print(e)
finally:
conn.close()
if __name__=="__main__":
server(8001)
2) 客户端
# Author : Xuefeng
import gevent
import socket
from gevent import monkey
# 设置端口号与地址名称
HOST = "localhost"
PORT = 9000
# 创建socket链接
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
msg = bytes(input(">>>:"),encoding="utf-8")
# 发送数据
s.sendall(msg)
# 接收数据
data = s.recv(1024)
# repr将数据格式化
print("Recieved:", repr(data))
# 关闭链接
s.close()