协程是用户态的线程,并非真正意义上的线程,
协程只有一个线程,看起来并发的效果是因为它利用了寄存器的上下文切换,
多线程和多进程比较消耗cpu资源,当遇到修改数据的时候,还会遇到死锁的问题。
协程是最大的发挥了cpu的单核能力,遇到io阻塞就切换,阻塞完成之后切换回来。
协程的好处:
- 跨平台
- 跨体系架构
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型
- 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
- 无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序:这一点和事件驱动一样,可以使用异步IO操作来解决
yield生成器实现协程的并发效果:
import time
"""
协程:是用户态的线程
yield单线程下实现并发效果
"""
#生产者
def producer(name):
con1.__next__()
con2.__next__()
count = 0
while count < 5:
time.sleep(1)
print("\033[31;1m[producer %s]\033[0m is making baozi..." % name)
con1.send(count)
con2.send(count)
count += 1
#消费者
def consumer(name):
print("准备开始吃包子...")
while True:
baozi = yield
print("[%s]吃了包子[%s]" % (name, baozi))
if __name__ == '__main__':
con1 = consumer("轩轩")
con2 = consumer("壮壮")
producer("小白")
greenlet代码示例:
from greenlet import greenlet
def homepage():
print("34")
gr2.switch()
print("12")
gr3.switch()
def bbs():
print("84")
gr3.switch()
print("13")
def login():
print("56")
gr1.switch()
print("--end--")
gr1 = greenlet(homepage)
gr2 = greenlet(bbs)
gr3 = greenlet(login)
执行结果:
34
84
56
12
--end--
gevent模块是greentlet模块的简单化:
import gevent
"""
自动档的协程
自上而下从0开始计数,如果计数不大于sleep数,则跳过后面代码
否则,执行后面代码
"""
def run1():
print("run1->1")
gevent.sleep(2)
print("run1->2")
def run2():
print("run2->1")
gevent.sleep(3)
print("run2->2")
def run3():
print("run3->1")
gevent.sleep(1)
print("run3->2")
def run4():
print("run4->1")
gevent.sleep(0)
print("run4->2")
#由上而下依次执行
gevent.joinall([
gevent.spawn(run1),
gevent.spawn(run2),
gevent.spawn(run3),
gevent.spawn(run4),
])
执行结果:
run1->1
run2->1
run3->1
run4->1
run4->2
run3->2
run1->2
run2->2
利用协程实现并发爬取网页内容:
import gevent, time
from gevent import monkey
monkey.patch_all()
from urllib import request
"""
利用协程,高效爬取网页
gevent模块默认不识别urllib的IO操作,
需要导入monkey给urllib里面的操作打上标记
"""
filename = "result.html"
#爬取网页内容到本地
def get_url_content(url):
print("GET: %s" % url)
res = request.urlopen(url)
html = res.read()
with open(filename, "wb") as f:
f.write(html)
print("%d bytes received from %s" % (len(html), url))
print("----------------------")
url_list = [
"https://www.python.org/",
"https://github.com/",
"https://www.bilibili.com/"
]
#启动时间
serial_start_time = time.time()
#串行的方式获取网页内容
for url in url_list:
get_url_content(url)
#计算花费时间
print("serial cost:", time.time() - serial_start_time)
#启动时间
async_start_time = time.time()
#并行的方式获取网页内容
gevent.joinall([
gevent.spawn(get_url_content, "https://www.python.org/"),
gevent.spawn(get_url_content, "https://github.com"),
gevent.spawn(get_url_content, "https://www.bilibili.com/"),
])
#计算花费时间
print("async cost:", time.time() - async_start_time)
协程实现高并发socket服务器:
服务端:
import gevent
from gevent import monkey
monkey.patch_all()
import socket
"""
协程实现socket的高并发服务器
"""
def handle_data(conn):
try:
while True:
data = conn.recv(1024)
if not data:
conn.shutdown(socket.SHUT_WR)
conn.send(data.upper())
except EXception as ex:
print(ex)
finally:
conn.close()
def my_server(port):
server = socket.socket()
#绑定地址和端口
server.bind(("0.0.0.0", port))
#开始监听
server.listen(500)
while True:
#阻塞等待连接
conn, addr = server.accept()
#来数据了
gevent.spawn(handle_data, conn)
if __name__ == '__main__':
my_server(6666)
客户端:
import socket
"""
协程实现socket高并发的客户端
"""
client = socket.socket()
client.connect(("localhost", 6666))
while True:
input_data = input(">>:").strip()
if not input_data:
continue
client.send(input_data.encode())
data = client.recv(1024).decode()
print(data)
client.close()