停止子线程

如果一切正常,那么上面的例子很完美。可是,需要停止程序,直接ctrl+c,会抛出KeyboardInterrupt错误,我们修改一下主循环:

try:
while True:
task = rcon.rpop("queue")
if not task:
time.sleep(1)
continue
asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)
except KeyboardInterrupt as e:
print(e)
new_loop.stop()

可是实际上并不好使,虽然主线程try了KeyboardInterrupt异常,但是子线程并没有退出,为了解决这个问题,可以设置子线程为守护线程,这样当主线程结束的时候,子线程也随机退出。

new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
t.setDaemon(True) # 设置子线程为守护线程
t.start() try:
while True:
# print('start rpop')
task = rcon.rpop("queue")
if not task:
time.sleep(1)
continue
asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)
except KeyboardInterrupt as e:
print(e)
new_loop.stop()

线程停止程序的时候,主线程退出后,子线程也随机退出才了,并且停止了子线程的协程任务

aiohttp

在消费队列的时候,我们使用asyncio的sleep用于模拟耗时的io操作。以前有一个短信服务,需要在协程中请求远程的短信api,此时需要是需要使用aiohttp进行异步的http请求。大致代码如下:

server.py

import time
from flask import Flask, request app = Flask(__name__) @app.route('/<int:x>')
def index(x):
time.sleep(x)
return "{} It works".format(x) @app.route('/error')
def error():
time.sleep(3)
return "error!" if __name__ == '__main__':
app.run(debug=True)

/接口表示短信接口,/error表示请求/失败之后的报警。

async-custoimer.py

import time
import asyncio
from threading import Thread
import redis
import aiohttp def get_redis():
connection_pool = redis.ConnectionPool(host='127.0.0.1', db=3)
return redis.Redis(connection_pool=connection_pool) rcon = get_redis() def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever() async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(resp.status)
return await resp.text() async def do_some_work(x):
print('Waiting ', x)
try:
ret = await fetch(url='http://127.0.0.1:5000/{}'.format(x))
print(ret)
except Exception as e:
try:
print(await fetch(url='http://127.0.0.1:5000/error'))
except Exception as e:
print(e)
else:
print('Done {}'.format(x)) new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
t.setDaemon(True)
t.start() try:
while True:
task = rcon.rpop("queue")
if not task:
time.sleep(1)
continue
asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)
except Exception as e:
print('error')
new_loop.stop()
finally:
pass

有一个问题需要注意,我们在fetch的时候try了异常,如果没有try这个异常,即使发生了异常,子线程的事件循环也不会退出。主线程也不会退出,暂时没找到办法可以把子线程的异常raise传播到主线程。(如果谁找到了比较好的方式,希望可以带带我)。

对于redis的消费,还有一个block的方法:

try:
while True:
_, task = rcon.brpop("queue")
asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)
except Exception as e:
print('error', e)
new_loop.stop()
finally:
pass

使用 brpop方法,会block住task,如果主线程有消息,才会消费。测试了一下,似乎brpop的方式更适合这种队列消费的模型。

127.0.0.1:6379[3]> lpush queue 5
(integer) 1
127.0.0.1:6379[3]> lpush queue 1
(integer) 1
127.0.0.1:6379[3]> lpush queue 1

可以看到结果

Waiting  5
Waiting 1
Waiting 1
200
1 It works
Done 1
200
1 It works
Done 1
200
5 It works
Done 5

协程消费

主线程用于监听队列,然后子线程的做事件循环的worker是一种方式。还有一种方式实现这种类似master-worker的方案。即把监听队列的无限循环逻辑一道协程中。程序初始化就创建若干个协程,实现类似并行的效果。

import time
import asyncio
import redis now = lambda : time.time() def get_redis():
connection_pool = redis.ConnectionPool(host='127.0.0.1', db=3)
return redis.Redis(connection_pool=connection_pool) rcon = get_redis() async def worker():
print('Start worker') while True:
start = now()
task = rcon.rpop("queue")
if not task:
await asyncio.sleep(1)
continue
print('Wait ', int(task))
await asyncio.sleep(int(task))
print('Done ', task, now() - start) def main():
asyncio.ensure_future(worker())
asyncio.ensure_future(worker()) loop = asyncio.get_event_loop()
try:
loop.run_forever()
except KeyboardInterrupt as e:
print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())
loop.stop()
loop.run_forever()
finally:
loop.close() if __name__ == '__main__':
main()

这样做就可以多多启动几个worker来监听队列。一样可以到达效果。

Python协程(下)的更多相关文章

  1. day-5 python协程与I/O编程深入浅出

    基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1.  什么是协程(以下内容来自维基百 ...

  2. 终结python协程----从yield到actor模型的实现

    把应用程序的代码分为多个代码块,正常情况代码自上而下顺序执行.如果代码块A运行过程中,能够切换执行代码块B,又能够从代码块B再切换回去继续执行代码块A,这就实现了协程 我们知道线程的调度(线程上下文切 ...

  3. 从yield 到yield from再到python协程

    yield 关键字 def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b yield 是在:PEP 255 -- Simple Generator ...

  4. 用yield实现python协程

    刚刚介绍了pythonyield关键字,趁热打铁,现在来了解一下yield实现协程. 引用官方的说法: 与线程相比,协程更轻量.一个python线程大概占用8M内存,而一个协程只占用1KB不到内存.协 ...

  5. [转载] Python协程从零开始到放弃

    Python协程从零开始到放弃 Web安全 作者:美丽联合安全MLSRC   2017-10-09  3,973   Author: lightless@Meili-inc Date: 2017100 ...

  6. 00.用 yield 实现 Python 协程

    来源:Python与数据分析 链接: https://mp.weixin.qq.com/s/GrU6C-x4K0WBNPYNJBCrMw 什么是协程 引用官方的说法: 协程是一种用户态的轻量级线程,协 ...

  7. python协程详解

    目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...

  8. Python协程与Go协程的区别二

    写在前面 世界是复杂的,每一种思想都是为了解决某些现实问题而简化成的模型,想解决就得先面对,面对就需要选择角度,角度决定了模型的质量, 喜欢此UP主汤质看本质的哲学科普,其中简洁又不失细节的介绍了人类 ...

  9. python 协程与go协程的区别

    进程.线程和协程 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定义: 操作系统能够进行运算调度的最小单位.它被包含在进 ...

  10. python协程详解,gevent asyncio

    python协程详解,gevent asyncio 新建模板小书匠 #协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1.生成器实现切换 [1] ...

随机推荐

  1. 修改tomcat的Response Hearder 头中的Server信息

    如图: Server: Apache-Coyote/1.1 这个信息给入侵者提供了一定的指示作用.为了安全起见,要求更改这个信息.那么我们就来修改一下试试,非常简单,只要在Connector中添加se ...

  2. Linux网络知识

    在思科上面模拟一下数据包的传递过程:一般上网使用的协议是tcp 交换机是一个2层的设备,它和Ip地址是没有关系的. 交换机上主要处理的是硬件地址(MAC),它只能分析到硬件地址,再到IP地址它就不管了 ...

  3. 编程笔记:JavaScript 中的类型检查

    在Badoo的时候我们写了大量的JS脚本,光是在我们的移动web客户端上面就有大概60000行,可想而知,维护这么多JS可是相当具有挑战性的.在写如上规模js脚本客户端应用的时候我们必须对一件事保持警 ...

  4. 【BZOJ】4766: 文艺计算姬

    [题目]给定两边节点数为n和m的完全二分图,求生成树数取模给定的p.n,m,p<=10^18. [算法]生成树计数(矩阵树定理) [题解]参考自 [bzoj4766]文艺计算姬 by WerKe ...

  5. NYOJ 129 树的判定 (并查集)

    题目链接 描述 A tree is a well-known data structure that is either empty (null, void, nothing) or is a set ...

  6. php简单文件管理器——php经典实例

    <html> <head> <title>文件管理</title> <meta charset='utf-8' /> </head&g ...

  7. Linux 查看网卡流量【转】

        我的系统式RHEL5.   在linux下,查看网卡流量的方法有很多.下面先记录几个,和他们的大概用法.已被以后之需.   一:iptraf   一个很不错的工具.RHEL5 iso自带有,我 ...

  8. tomcat已启动,使用maven的deploy发布后,根据路径打开浏览器访问时报错HTTP Status 500 - Error instantiating servlet class

    web项目中请求出现错误,如下: HTTP Status 500 - Error instantiating servlet class XXXX类 type Exception report mes ...

  9. django时差8个小时问题

    问题现象: 在用django做好的网站,上传图片后显示的发布时间比当前时间差了8小时 查找问题: 查看服务器系统时间,经查与当前时间一致,无问题 查看数据库中的时间也一样 最终原因: 在setting ...

  10. [VBA]批量替换PPT里的字体颜色

    不知道为什么计组老师的大量课件字体是伤害视力的亮蓝色……看久了眼睛疼,想把颜色替换成保护视力一点的灰色,但是找了N久也没找到在图形界面上直接操作的方法,于是在MSDN上晃了晃,Google了一下,写了 ...