协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
与线程相比,协程更轻量。一个Python线程大概占用8M内存,而一个协程只占用1KB不到内存。协程更适用于IO密集型的应用。
实例:
def jumping_range(N):
index = 0
while index < N:
# 将send()发送的信息赋值给jump
jump = yield index
if jump is None:
jump = 1
index += jump
if __name__ == '__main__':
itr = jumping_range(5) #生成器函数,遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值
print(next(itr)) #index为0
print(itr.send(2)) #send()方法将值赋给jump
print(next(itr)) #index = 1+2
print(itr.send(-1)) #index = 3+(-1)
执行输出结果如下:
0
2
3
2
为什么要使用协程?
举个例子:
假如我们做一个爬虫。我们要爬取多个网页,这里简单举例两个网页(两个spider函数),获取HTML(耗IO),然后再对HTML对行解析取得我们感兴趣的数据。
如果能在get_html()
这里暂停一下,不用傻乎乎地去等待网页返回,而是去做别的事。等过段时间再回过头来到刚刚暂停的地方,接收返回的html内容,然后还可以接下去解析parse_html(html)
。
试着思考一下,假如没有协程,我们要写一个并发程序。可能有以下问题
1)使用最常规的同步编程要实现异步并发效果并不理想,或者难度极高。
2)由于GIL锁的存在,多线程的运行需要频繁的加锁解锁,切换线程,这极大地降低了并发性能;
而协程的出现,刚好可以解决以上的问题。它的特点有
- 协程是在单线程里实现任务的切换的
- 利用同步的方式去实现异步
- 不再需要锁,提高了并发性能
yield from
yield from
是在Python3.3才出现的语法,yield from
后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。
yield和yield from 对比:
使用yield:
# 字符串
astr='ABC'
# 列表
alist=[1,2,3]
# 字典
adict={"name":"wangbm","age":18}
# 生成器
agen=(i for i in range(4,8))
def gen(*args, **kw):
for item in args:
for i in item:
yield i
new_list=gen(astr, alist, adict, agen)
print(list(new_list))
#输出结果:
['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
使用yield from:
#字符串
astr='ABC'
# 列表
alist=[1,2,3]
# 字典
adict={"name":"wangbm","age":18}
# 生成器
agen=(i for i in range(4,8))
def gen(*args, **kw):
for item in args:
yield from item
new_list=gen(astr, alist, adict, agen)
print(list(new_list))
#输出结果:
['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。
当 yield from
后面加上一个生成器后,就实现了生成的嵌套。如果自己用yield
去实现,那只会加大代码的编写难度,降低开发效率,降低代码的可读性。
生成器嵌套相关概念:
1、
调用方
:调用委派生成器的客户端(调用方)代码
2、委托生成器
:包含yield from表达式的生成器函数
3、子生成器
:yield from后面加的生成器函数
举个计算平均数的例子:
# 子生成器(平均数)
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
count += 1
total += new_num
average = total/count
# 委托生成器
def proxy_gen():
while True:
yield from average_gen()
if __name__ == '__main__':
calc_average = proxy_gen() # 调用方
next(calc_average) # 预激活生成器,保存average为0
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
输出结果如下:
10.0
20.0
30.0
委托生成器的作用是:在调用方与子生成器之间建立一个双向通道
。即调用方可以通过send()
直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。
还可以在yield from
前面看到可以赋值。
实例:
# 子生成器
def average_gen():
total,count,average = 0,0,0
while True:
new_num = yield average
if new_num is None:
break
count += 1
total += new_num
average = total/count
# 每一次return,都意味着当前协程结束。
return total,count,average
# 委托生成器
def proxy_gen():
while True:
# 只有子生成器return了,yield from左边的变量才会被赋值,后面的代码才会执行。
total, count, average = yield from average_gen()
print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))
if __name__ == '__main__':
calc_average = proxy_gen()
next(calc_average) # 预激活协程
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
calc_average.send(None) # 结束协程
# 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程
输出结果如下:
10.0
15.0
20.0
计算完毕!!
总共传入 3 个数值, 总和:60,平均数:20.0
yield from 有什么过人之处,让我们非要用它不可————因为它可以帮我们处理异常
具体yield from
为我们做了哪些事?结合以下说明。
- 迭代器(即可指子生成器)产生的值直接返还给调用者
- 任何使用send()方法发给委派生产器(即外部生产器)的值被直接传递给迭代器。如果send值是None,则调用迭代器next()方法;如果不为None,则调用迭代器的send()方法。如果对迭代器的调用产生StopIteration异常,委派生产器恢复继续执行yield from后面的语句;若迭代器产生其他任何异常,则都传递给委派生产器。
- 子生成器可能只是一个迭代器,并不是一个作为协程的生成器,所以它不支持.throw()和.close()方法,即可能会产生AttributeError 异常。
- 除了GeneratorExit 异常外的其他抛给委派生产器的异常,将会被传递到迭代器的throw()方法。如果迭代器throw()调用产生了StopIteration异常,委派生产器恢复并继续执行,其他异常则传递给委派生产器。
- 如果GeneratorExit异常被抛给委派生产器,或者委派生产器的close()方法被调用,如果迭代器有close()的话也将被调用。如果close()调用产生异常,异常将传递给委派生产器。否则,委派生产器将抛出GeneratorExit 异常。
- 当迭代器结束并抛出异常时,yield from表达式的值是其StopIteration 异常中的第一个参数。
- 一个生成器中的return expr语句将会从生成器退出并抛出 StopIteration(expr)异常。
asyncio
asyncio
是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
1. asyncio的几个概念
event_loop 事件循环
:程序开启一个无限的循环,程序员会把一些函数(协程)注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。coroutine 协程
:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。future 对象
: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别task 任务
:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。Task 对象是 Future 的子类,它将 coroutine 和 Future 联系在一起,将 coroutine 封装成一个 Future 对象。async/await 关键字
:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。其作用在一定程度上类似于yield。
2. 如何定义/创建协程
在一个函数前面加上 async
关键字,这个函数对象是一个协程(通过isinstance
函数可以判断确实是Coroutine
类型。)
生成器是协程的基础,如何将一个生成器,直接变成协程使用呢?
import asyncio
from collections.abc import Generator, Coroutine
'''只要在一个生成器函数头部用上 @asyncio.coroutine 装饰器
就能将这个函数对象,【标记】为协程对象。注意这里是【标记】,划重点。
实际上,它的本质还是一个生成器。
标记后,它实际上已经可以当成协程使用。'''
@asyncio.coroutine
def hello():
# 异步调用asyncio.sleep(1):
yield from asyncio.sleep(1)
if __name__ == '__main__':
coroutine = hello() # 生成协程对象,并不会运行函数内的代码
print(isinstance(coroutine, Generator)) # True
print(isinstance(coroutine, Coroutine)) # False
协程完整的工作流程:
- 定义/创建协程对象
- 将协程转为task任务
- 定义事件循环对象容器
- 将task任务扔进事件循环对象中触发
那我们就按照流程来实现协程,举例:
import asyncio
async def hello(name):
print('Hello,', name)
# 定义协程对象
coroutine = hello("World")
# 定义事件循环对象容器
loop = asyncio.get_event_loop()
# 将协程转为task任务
task = loop.create_task(coroutine)
# 将task任务扔进事件循环对象中并触发
loop.run_until_complete(task)
输出结果如下:
Hello, World
3. await与yield对比
前面我们说,await
用于挂起阻塞的异步调用接口。其作用在一定程度上
类似于yield。
值得注意的是在一定程度上,意思是效果上一样(都能实现暂停的效果),但是功能上却不兼容。
就是你不能在生成器中使用await
,也不能在async 定义的协程中使用yield
。
普通函数(生成器)中不能使用 await:
async 中 不能使用yield:
除此之外呢,还有一点很重要的。
yield from
后面可接可迭代对象
,也可接future对象
/协程对象;await
后面必须要接future对象
/协程对象
如何验证呢?
之前说过yield from
后面可接 可迭代对象。
接下来,就只要验证,yield from
和await
都可以接future对象
/协程对象
就可以了。
验证之前呢,要先介绍一下函数asyncio.sleep(n)
,这是asyncio自带的工具函数,他可以模拟IO阻塞,他返回的是一个协程对象。
func = asyncio.sleep(2)
print(isinstance(func, Future)) # False
print(isinstance(func, Coroutine)) # True
还有,要学习如何创建Future对象
,前面概念里说过,Task是Future的子类,这么说,我们只要创建一个task对象即可:
import asyncio
from asyncio.futures import Future
async def hello(name):
await asyncio.sleep(2)
print('Hello, ', name)
coroutine = hello("World")
# 将协程转为task对象
task = asyncio.ensure_future(coroutine)
print(isinstance(task, Future)) # True
好了,接下来,开始验证:
4. 绑定回调函数
异步IO的实现原理,就是在IO高的地方挂起,等IO结束后,再继续执行。
在绝大部分时候,我们后续的代码的执行是需要依赖IO的返回值的,这就要用到回调函数了。
回调的实现,有两种:
一种是绝大部分程序员喜欢的,利用的同步编程实现的回调,这要求我们要能够有办法取得协程的await的返回值:
import asyncio
import time
async def _sleep(x):
time.sleep(2)
return '暂停了{}秒!'.format(x)
coroutine = _sleep(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
# task.result() 可以取得返回结果
print('返回结果:{}'.format(task.result()))
输出:
返回结果:暂停了2秒!
还有一种是通过asyncio自带的添加回调函数功能来实现:
import time
import asyncio
async def _sleep(x):
time.sleep(2)
return '暂停了{}秒!'.format(x)
def callback(future):
print('这里是回调函数,获取返回结果是:', future.result())
coroutine = _sleep(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
# 添加回调函数
task.add_done_callback(callback)
loop.run_until_complete(task)
输出:
这里是回调函数,获取返回结果是: 暂停了2秒!
5、协程中的并发
协程的并发,和线程一样。asyncio
实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。
第一步,当然是创建多个协程的列表:
# 协程函数
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
# 协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)
# 将协程转成task,并组成list
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
第二步,将这些协程注册到事件循环中去:
有两种方法:
- 使用
asyncio.wait()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
- 使用
asyncio.gather()
# 千万注意,这里的 「*」 不能省略
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))
最后,return的结果,可以用task.result()
查看。
for task in tasks:
print('Task ret: ', task.result())
完整代码如下:
import asyncio
# 协程函数
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
# 协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)
# 将协程转成task,并组成list
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
输出:
Waiting: 1
Waiting: 2
Waiting: 4
Task ret: Done after 1s
Task ret: Done after 2s
Task ret: Done after 4s
for task in tasks:
print('Task ret: ', task.result())
6、协程中的嵌套
使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。
举个栗子:
import asyncio
# 用于内部的协程函数
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
# 外部的协程函数
async def main():
# 创建三个协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)
# 将协程转为task,并组成list
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
# 【重点】:await 一个task列表(协程)
# dones:表示已经完成的任务
# pendings:表示未完成的任务
dones, pendings = await asyncio.wait(tasks)
for task in dones:
print('Task ret: ', task.result())
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
如果这边,使用的是asyncio.gather()
,是这么用的:
# 注意这边返回结果,与await不一样
results = await asyncio.gather(*tasks)
for result in results:
print('Task ret: ', result)
输出还是一样的:
Waiting: 1
Waiting: 2
Waiting: 4
Task ret: Done after 1s
Task ret: Done after 2s
Task ret: Done after 4s
可以发现这个例子完全是由 上面「协程中的并发
」例子改编而来。
结果完全一样。只是把创建协程对象,转换task任务,封装成在一个协程函数里而已。外部的协程,嵌套了一个内部的协程。
其实你如果去看下asyncio.await()
的源码的话,你会发现下面这种写法看似没有嵌套,实际上内部也是嵌套的:
loop.run_until_complete(asyncio.wait(tasks))
7、协程中的状态
对于生成器有生成器的状态,同样,协程(准确的说,应该是Future对象,或者Task任务)有以下四种状态:
Pending
:创建future,还未执行Running
:事件循环正在调用执行任务Done
:任务执行完毕Cancelled
:Task被取消后的状态
可在终端下用命令 python3 xx.py
执行下面这段代码:
import asyncio
import threading
import time
async def hello():
print("Running in the loop...")
flag = 0
while flag < 1000:
with open("F:\\test.txt", "a") as f:
f.write("------")
flag += 1
print("Stop the loop")
if __name__ == '__main__':
coroutine = hello()
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
# Pending:未执行状态
print(task)
try:
t1 = threading.Thread(target=loop.run_until_complete, args=(task,))
# t1.daemon = True
t1.start()
# Running:运行中状态
time.sleep(1)
print(task)
t1.join()
except KeyboardInterrupt as e:
# 取消任务
task.cancel()
# Cacelled:取消任务
print(task)
finally:
print(task)
顺利执行的话,将会打印 Pending
-> Pending:Runing
-> Finished
的状态变化。假如,执行后 立马按下 Ctrl+C,则会触发task取消,就会打印 Pending
-> Cancelling
-> Cancelling
的状态变化。
8、gather与wait的对比
把多个协程注册进一个事件循环中有两种方法:
- 使用
asyncio.wait()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
- 使用
asyncio.gather()
# 千万注意,这里的 「*」 不能省略
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))
asyncio.gather
和 asyncio.wait
在asyncio中用得的比较广泛,现在对这两种方法进行对比研究。
我们用例子来说明,先定义一个协程函数:
import asyncio
async def factorial(name, number):
f = 1
for i in range(2, number+1):
print("Task %s: Compute factorial(%s)..." % (name, i))
await asyncio.sleep(1)
f *= i
print("Task %s: factorial(%s) = %s" % (name, number, f))
(1)asyncio.wait()
接收的tasks,必须是一个list(列表)对象,这个list(列表)对象里,存放多个的task。
可以用asyncio.ensure_future
转为task对象:
tasks=[
asyncio.ensure_future(factorial("A", 2)),
asyncio.ensure_future(factorial("B", 3)),
asyncio.ensure_future(factorial("C", 4))
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
也可以不转为task对象。
tasks=[
factorial("A", 2),
factorial("B", 3),
factorial("C", 4)
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
asyncio.wait返回
asyncio.wait
返回dones
和pendings
dones
:表示已经完成的任务pendings
:表示未完成的任务
如果我们需要获取,运行结果,需要自己去收集获取:
dones, pendings = await asyncio.wait(tasks)
for task in dones:
print('Task ret: ', task.result())
wait还具有控制功能
import asyncio
import random
async def coro(tag):
await asyncio.sleep(random.uniform(0.5, 5))
loop = asyncio.get_event_loop()
tasks = [coro(i) for i in range(1, 11)]
# 【控制运行任务数】:运行第一个任务就返回
# FIRST_COMPLETED :第一个任务完全返回
# FIRST_EXCEPTION:产生第一个异常返回
# ALL_COMPLETED:所有任务完成返回 (默认选项)
dones, pendings = loop.run_until_complete(
asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED))
print("第一次完成的任务数:", len(dones))
# 【控制时间】:运行一秒后,就返回
dones2, pendings2 = loop.run_until_complete(
asyncio.wait(pendings, timeout=1))
print("第二次完成的任务数:", len(dones2))
# 【默认】:所有任务完成后返回
dones3, pendings3 = loop.run_until_complete(asyncio.wait(pendings2))
print("第三次完成的任务数:", len(dones3))
loop.close()
输出结果如下:
第一次完成的任务数: 1
第二次完成的任务数: 4
第三次完成的任务数: 5
(2)asyncio.gather()
接收的就比较广泛了,他可以接收list对象,但是 *
不能省略
tasks=[
asyncio.ensure_future(factorial("A", 2)),
asyncio.ensure_future(factorial("B", 3)),
asyncio.ensure_future(factorial("C", 4))
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))
因为asyncio.gather()
的第一个参数是 *coros_or_futures
,它叫 非命名键值可变长参数列表
,可以整合所有没有命名的变量,就可以下面这样:
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
))
甚至还可以这样:
loop = asyncio.get_event_loop()
group1 = asyncio.gather(*[factorial("A" ,i) for i in range(1, 3)])
group2 = asyncio.gather(*[factorial("B", i) for i in range(1, 5)])
group3 = asyncio.gather(*[factorial("B", i) for i in range(1, 7)])
loop.run_until_complete(asyncio.gather(group1, group2, group3))
asyncio.gather()
会把值直接返回给我们,不需要自己去收集获取。
results = await asyncio.gather(*tasks)
for result in results:
print('Task ret: ', result)
下面我们将通过一个小实战,来对这些内容进行巩固。
在实战中,将会用到以下知识点:
- 多线程的基本使用
- Queue消息队列的使用
- Redis的基本使用
- asyncio的使用
9、动态添加协程
在实战之前,我们必须首先要学会在asyncio中如何将协程动态的添加到事件循环中的。
这如何实现呢,有两种方法:
- 主线程是
同步
的
import time
import asyncio
from queue import Queue
from threading import Thread
def start_loop(loop):
# 一个在后台永远运行的事件循环
asyncio.set_event_loop(loop)
loop.run_forever()
def do_sleep(x, queue, msg=""):
time.sleep(x)
queue.put(msg)
queue = Queue()
new_loop = asyncio.new_event_loop()
# 定义一个线程,并传入一个事件循环对象
t = Thread(target=start_loop, args=(new_loop,))
t.start()
print(time.ctime())
# 动态添加两个协程
# 这种方法,在主线程是同步的
new_loop.call_soon_threadsafe(do_sleep, 6, queue, "第一个")
new_loop.call_soon_threadsafe(do_sleep, 3, queue, "第二个")
while True:
msg = queue.get()
print("{} 协程运行完..".format(msg))
print(time.ctime())
输出:
Thu May 31 22:11:16 2018
第一个 协程运行完..
Thu May 31 22:11:22 2018
第二个 协程运行完..
Thu May 31 22:11:25 2018
由于是同步的,所以总共耗时6+3=9
秒.
- 主线程是
异步
的,这是重点,一定要掌握。
import time
import asyncio
from queue import Queue
from threading import Thread
def start_loop(loop):
# 一个在后台永远运行的事件循环
asyncio.set_event_loop(loop)
loop.run_forever()
async def do_sleep(x, queue, msg=""):
await asyncio.sleep(x)
queue.put(msg)
queue = Queue()
new_loop = asyncio.new_event_loop()
# 定义一个线程,并传入一个事件循环对象
t = Thread(target=start_loop, args=(new_loop,))
t.start()
print(time.ctime())
# 动态添加两个协程
# 这种方法,在主线程是异步的
asyncio.run_coroutine_threadsafe(do_sleep(6, queue, "第一个"), new_loop)
asyncio.run_coroutine_threadsafe(do_sleep(3, queue, "第二个"), new_loop)
while True:
msg = queue.get()
print("{} 协程运行完..".format(msg))
print(time.ctime())
输出:
Thu May 31 22:23:35 2018
第二个 协程运行完..
Thu May 31 22:23:38 2018
第一个 协程运行完..
Thu May 31 22:23:41 2018
由于是异步的,所以总共耗时max(6, 3)=6
秒
实战:利用redis实现动态添加任务
对于并发任务,通常是用生成消费模型,对队列的处理可以使用类似master-worker的方式,master主要用户获取队列的msg消息,worker用户来处理消息。
为了简单起见,并且协程更适合单线程的方式,我们的主线程用来监听队列,子线程用于处理队列。
这里使用redis的队列。主线程中有一个是无限循环,用户消费队列。
先到 https://github.com/MicrosoftArchive/redis/releases 下载 redis 并解压到你的路径。在当前路径运行cmd,相继运行Redis服务器(命令.\redis-server.exe)和Redis客户端(命令.\redis-cli.exe)。
并依次输入key=queue,value=5,3,1的消息。
一切准备就绪之后,我们就可以运行我们的代码了。
import time
import redis
import asyncio
from queue import Queue
from threading import Thread
def start_loop(loop):
# 一个在后台永远运行的事件循环
asyncio.set_event_loop(loop)
loop.run_forever()
async def do_sleep(x, queue):
await asyncio.sleep(x)
queue.put("ok")
def get_redis():
connection_pool = redis.ConnectionPool(host='127.0.0.1', db=0)
return redis.Redis(connection_pool=connection_pool)
def consumer():
while True:
task = rcon.rpop("queue")
if not task:
time.sleep(1)
continue
asyncio.run_coroutine_threadsafe(do_sleep(int(task), queue), new_loop)
if __name__ == '__main__':
print(time.ctime())
new_loop = asyncio.new_event_loop()
# 定义一个线程,运行一个事件循环对象,用于实时接收新任务
loop_thread = Thread(target=start_loop, args=(new_loop,))
loop_thread.setDaemon(True)
loop_thread.start()
# 创建redis连接
rcon = get_redis()
queue = Queue()
# 子线程:用于消费队列消息,并实时往事件对象容器中添加新任务
consumer_thread = Thread(target=consumer)
consumer_thread.setDaemon(True)
consumer_thread.start()
while True:
msg = queue.get()
print("协程运行完..")
print("当前时间:", time.ctime())
输出:
Thu May 31 23:42:48 2018
协程运行完..
当前时间: Thu May 31 23:42:49 2018
协程运行完..
当前时间: Thu May 31 23:42:51 2018
协程运行完..
当前时间: Thu May 31 23:42:53 2018
我们看下上面这部分代码,稍微介绍一下:
loop_thread
:单独的线程,运行着一个事件对象容器,用于实时接收新任务。consumer_thread
:单独的线程,实时接收来自Redis的消息队列,并实时往事件对象容器中添加新任务。
我们在Redis,分别发起了5s
,3s
,1s
的任务。
从结果来看,这三个任务,确实是并发执行的,1s
的任务最先结束,三个任务完成总耗时5s
。
运行后,程序是一直运行在后台的,我们每一次在Redis中输入新值,都会触发新任务的执行。