Python并发编程——协程
2021-05-16 本文已影响0人
xiaogp
摘要:Python
,协程
,gevent
协程基本概念
协程,又称微线程,纤程。英文名Coroutine,是Python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元。 它自带CPU上下文。这样只要在合适的时机, 可以把一个协程切换到另一个协程。,切换的次数以及什么时候再切换由开发者自己确定
(1)协程和线程差异
在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存的数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住,简单而言协程的特点是
- 线程之间需要上下文切换成本相对协程来说是比较高的,尤其在开启线程较多时,但协程的切换成本非常低。
- 同样的线程的切换更多的是靠操作系统来控制,而协程的执行由开发者自己控制
(2)协程对比进程线程
- 协程用户层面的概念,是由用户通过代码自定义的
- 进程线程都是操作系统层面的概念,由计算机自己实现
Python中协程的实现
主要整理Python中使用gevent
实现协程的代码实现,gevent是一个基于协程的Python网络库,在遇到IO阻塞时,程序会自动进行切换,可以让开发者用同步的方式写异步IO代码
(1)gevent的基本使用
gevent通过gevent.spawn
创建协程任务,传入执行函数,可以给执行函数传入多个参数,使用joinall
启动任务且主进程等待协程执行完毕退出
import time
import datetime
import gevent
def my_func(sid, sid2):
time.sleep(3)
print("sid:{}, sid2:{}, time:{}".format(sid, sid2, datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S")))
t1 = gevent.spawn(my_func, sid="a", sid2="b") # t1 = gevent.spawn(my_func, "a", "b")
t2 = gevent.spawn(my_func, sid="a", sid2="b")
gevent.joinall([t1, t2])
输出如下,没有达到并行效果,还是顺序执行,需要增加gevent.monkey
补丁
sid:a, sid2:b, time:2021-05-16 20:10:51
sid:a, sid2:b, time:2021-05-16 20:10:54
加入补丁如下
import time
import datetime
import gevent
from gevent import monkey
monkey.patch_all()
def my_func(sid, sid2):
time.sleep(3)
print("sid:{}, sid2:{}, time:{}".format(sid, sid2, datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S")))
t1 = gevent.spawn(my_func, sid="a", sid2="b")
t2 = gevent.spawn(my_func, sid="a", sid2="b")
gevent.joinall([t1, t2])
输出如下
sid:a, sid2:b, time:2021-05-16 20:14:19
sid:a, sid2:b, time:2021-05-16 20:14:19
(2)协程池的使用
协程池相对要好用些,可以自定义一个协程池指定个数,把待执行的列表丢进去,即可执行
import time
import datetime
import gevent
from gevent import monkey
from gevent.pool import Pool
monkey.patch_all()
def my_func(sid, sid2):
time.sleep(3)
print("sid:{}, sid2:{}, time:{}".format(sid, sid2, datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S")))
if __name__ == '__main__':
pool = Pool(4)
jobs = [["a", "b"], ["b", "c"], ["c", "d"], ["d", "e"]]
for job in jobs:
pool.apply_async(my_func, job)
pool.join()
如果函数有输出值,则可以使用map
获得协程池的执行结果,gevent的协程池不支持map传入多个执行函数的参数,可以在执行函数中解包如下
import time
import datetime
import gevent
from gevent import monkey
from gevent.pool import Pool
monkey.patch_all()
def my_func(sid: tuple):
sid1 = sid[0]
sid2 = sid[1]
time.sleep(3)
print("sid:{}, sid2:{}, time:{}".format(sid1, sid2, datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S")))
return str(sid1) + str(sid2)
if __name__ == '__main__':
pool = Pool(4)
jobs = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e")]
res = pool.map(my_func, jobs)
pool.join()
print(res)
输出如下
sid:a, sid2:b, time:2021-05-16 22:09:40
sid:b, sid2:c, time:2021-05-16 22:09:40
sid:c, sid2:d, time:2021-05-16 22:09:40
sid:d, sid2:e, time:2021-05-16 22:09:40
['ab', 'bc', 'cd', 'de']