本篇文章给大家带来的内容是关于python协程的详细讲解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
进程和线程都会的切换都要消耗时间,保存线程进程当前状态以便下次继续执行。在不怎么需要cpu的程序中,即相对于IO密集型的程序,协程相对于线程进程资源消耗更小,切换更快,更适用于IO密集型。协程也是单线程的,没法利用cpu的多核,想利用cpu多核可以通过,进程+协程的方式,又或者进程+线程+协程。
1、协程的简单实现
协程的原理是通过生成器实现,如下:程序执行到19行,执行consumer函数到13行,next生成器,执行producer函数到8行停下,返回consumer函数13行继续往下执行,循环一次再次来到13行,生成器函数将从上次yield停下的地方往下执行。循环如此就完成就完成了并发的效果。
但有人可能会说我用循环顺序执行两个函数结果也一样啊,如下第2例子。当这里想说的是,这样并不能保留函数的执行的位置,只是简单的一个函数执行结束换到另一个函数而已,遇到需要cpu等待的操作也没法切换。在遇到需要cpu等待的操作主动让出cpu,记住函数执行的位置,下次切换回来继续执行才能算是并发的运行,提高程序的并发效果。
立即学习“Python免费学习笔记(深入)”;
协程简单实现生产者与消费者
import time
def producer():
while True:
time.sleep(1)
print("+++++ 1个包子", time.strftime("%X"))
yield
def consumer():
while True:
next(prd)
print("----- 1个包子", time.strftime("%X"))
if __name__ == "__main__":
prd = producer()
consumer()
# 输出结果
+++++ 1个包子 16:22:30
----- 1个包子 16:22:30
+++++ 1个包子 16:22:31
----- 1个包子 16:22:31
+++++ 1个包子 16:22:32
----- 1个包子 16:22:32
+++++ 1个包子 16:22:33
----- 1个包子 16:22:33顺序执行效果
import time
def producer():
time.sleep(1)
print("+++++ 1个包子", time.strftime("%X"))
def consumer():
print("----- 1个包子", time.strftime("%X"))
if __name__ == "__main__":
while True:
producer()
consumer()
# 输出结果
+++++ 1个包子 16:22:30
----- 1个包子 16:22:30
+++++ 1个包子 16:22:31
----- 1个包子 16:22:31
+++++ 1个包子 16:22:32
----- 1个包子 16:22:32
+++++ 1个包子 16:22:33
----- 1个包子 16:22:332、greenlet
greenlet模块需要安装,pip install greenlet。greenlet原理是对生成器的封装。greenlet类提供了一个方法,switch:在需要进行切换的时候切换到指定的协程。
from greenlet import greenlet
import time
def producer():
while True:
time.sleep(1)
print("+++++ 1个包子", time.strftime("%X"))
gr2.switch() # 切换到gr2运行
def consumer():
while True:
print("----- 1个包子", time.strftime("%X"))
gr1.switch() # 切换到gr1运行
if __name__ == "__main__":
gr1 = greenlet(producer)
gr2 = greenlet(consumer)
gr1.switch() # 切换到gr1运行
# 输出结果
+++++ 1个包子 09:39:45
----- 1个包子 09:39:45
+++++ 1个包子 09:39:46
----- 1个包子 09:39:46
+++++ 1个包子 09:39:47
----- 1个包子 09:39:473、gevent
gevent模块也需要安装,pip install gevent。gevent是对gevent的再次封装,能自动识别耗时操作切换到其它协程。注意gevent遇到耗时操作才会切换协程运行,没有遇到耗时操作是不会主动切换的。
gevent.spawn(*args, **kwargs) 不定长参数中的第一个参数为协程执行的方法fn,其余的依次为 fn 的参数。开启了协程后要调用join方法。
gevent模块中识别耗时的操作有两种方式,① 使用gevent模块中重写的类。如,gevent.socket gevent.sleep ② 打补丁的方式,在所有的代码前。from gevent import monkey 导入这个模块,monkey.patch_all()调用这个方法。
推荐使用第二种方式,这样就不用更改已经写好的代码
正常情况下gevent并不会识别耗时操作
import time
import gevent
def producer():
for i in range(3):
time.sleep(1)
print("+++++ 1个包子", name, time.strftime("%X"))
def consumer():
for i in range(3):
time.sleep(1)
print("----- 1个包子", name, time.strftime("%X"))
if __name__ == "__main__":
g1 = gevent.spawn(producer, "zhangsan")
g2 = gevent.spawn(consumer, "lisi")
g1.join()
g2.join()
# 输出结果
+++++ 1个包子 zhangsan 10:42:38
+++++ 1个包子 zhangsan 10:42:39
+++++ 1个包子 zhangsan 10:42:40
----- 1个包子 lisi 10:42:41
----- 1个包子 lisi 10:42:42
----- 1个包子 lisi 10:42:43gevent识别耗时操作方式1,使用gevent中的模块
import time
import gevent
def producer():
for i in range(3):
gevent.sleep(1)
print("+++++ 1个包子", time.strftime("%X"))
def consumer():
for i in range(3):
gevent.sleep(1)
print("----- 1个包子", time.strftime("%X"))
if __name__ == "__main__":
g1 = gevent.spawn(producer)
g2 = gevent.spawn(consumer)
g1.join()
g2.join()
# 输出结果
+++++ 1个包子 10:43:04
----- 1个包子 10:43:04
+++++ 1个包子 10:43:05
----- 1个包子 10:43:05
+++++ 1个包子 10:43:06
----- 1个包子 10:43:06gevent识别耗时操作方式2,打补丁
import time
import gevent
from gevent import monkey
monkey.patch_all()
def producer():
for i in range(3):
time.sleep(1)
print("+++++ 1个包子", time.strftime("%X"))
def consumer():
for i in range(3):
time.sleep(1)
print("----- 1个包子", time.strftime("%X"))
if __name__ == "__main__":
g1 = gevent.spawn(producer)
g2 = gevent.spawn(consumer)
g1.join()
g2.join()
# 输出结果
+++++ 1个包子 10:44:04
----- 1个包子 10:44:04
+++++ 1个包子 10:44:05
----- 1个包子 10:44:05
+++++ 1个包子 10:44:06
----- 1个包子 10:44:06当开启的协程很多的时候,一个个的调用join方法就有点麻烦,所以gevent提供了一个方法joinall(),可以一次join所有的协程。joinall() 方法传参一个列表,列表包含了所有的协程。
joinall
import time
import gevent
from gevent import monkey
monkey.patch_all()
def producer(name):
for i in range(3):
time.sleep(1)
print("+++++ 1个包子", name, time.strftime("%X"))
def consumer(name):
for i in range(3):
time.sleep(1)
print("----- 1个包子", name, time.strftime("%X"))
if __name__ == "__main__":
gevent.joinall([gevent.spawn(producer, "zhangsan"), gevent.spawn(consumer, "lisi")])
# 输出结果
+++++ 1个包子 zhangsan 10:51:34
----- 1个包子 lisi 10:51:34
+++++ 1个包子 zhangsan 10:51:35
----- 1个包子 lisi 10:51:35
+++++ 1个包子 zhangsan 10:51:36
----- 1个包子 lisi 10:51:364、协程应用,并发服务器
服务端收到客户端消息,并原样发送回去
server
import socket
import gevent
from gevent import monkey
monkey.patch_all()
def fn(conn):
msg = conn.recv(1024).decode("utf-8")
print("服务的收到>>>", msg)
conn.send(msg.encode("utf-8"))
sk = socket.socket()
sk.bind(("127.0.0.1", 8899))
sk.listen()
while True:
conn, addr = sk.accept()
print("已连接服务器-->", addr)
gevent.spawn(fn, conn)
sk.close()
# 输出结果
已连接服务器--> ('127.0.0.1', 53878)
已连接服务器--> ('127.0.0.1', 53879)
已连接服务器--> ('127.0.0.1', 53880)
服务的收到>>> client1
服务的收到>>> client2
服务的收到>>> client3clien
import socket
sk = socket.socket()
sk.connect(("127.0.0.1", 8899))
msg = input("客户端发送的内容>>> ")
sk.send(msg.encode("utf-8"))
msg = sk.recv(1024).decode("utf-8")
print("客户端收到>>>", msg)
sk.close()
# 输出结果
客户端发送的内容>>> client1
客户端收到>>> client1以上就是python协程的详细讲解(附示例)的详细内容,更多请关注php中文网其它相关文章!
python怎么学习?python怎么入门?python在哪学?python怎么学才快?不用担心,这里为大家提供了python速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号