Build a Simple Message Queue (Async Task Queue) Yourself: Python Implementation
Another reinventing-the-wheel series. There are many mature, well-developed async task queue frameworks available for Python, such as Celery or RQ, but none of them come with a built-in message queue service—they all require a message queue like Redis or RabbitMQ. I don't need all that extra stuff for a small project, so I decided to implement one myself. Approach Add tasks that need to run asynchronously to a queue Automatically take tasks from the queue and create new threads to execute them Save the task execution results and output When a task completes, call a callback function to handle the returned data Use output
目录8 个章节
Another reinventing-the-wheel series. There are many mature, well-developed async task queue frameworks available for Python, such as Celery or RQ, but none of them come with a built-in message queue service—they all require a message queue like Redis or RabbitMQ. I don't need all that extra stuff for a small project, so I decided to implement one myself.
Approach
- Add tasks that need to run asynchronously to a queue
- Automatically take tasks from the queue and create new threads to execute them
- Save the task execution results and output
- When a task completes, call a callback function to handle the returned data
- Use output redirection to handle task output
Below I'll analyze the code for a few key parts.
Priority Queue
Python's standard library already has a priority queue implementation, but when objects of equal priority are passed in, it automatically compares those objects. Our Task class doesn't implement the relevant operator overloads, so they can't be compared.
To solve this, we could either implement operator overloading in Task, or implement a priority queue ourselves. I chose the latter.
class PriorityQueue:
"""
自己实现的优先级队列,使用了Python的堆
"""
def __init__(self):
self._index = 0
self.queue = []
def push(self, priority, val):
heapq.heappush(self.queue, (priority, self._index, val))
self._index += 1
def pop(self):
return heapq.heappop(self.queue)[-1]
@property
def empty(self):
return len(self.queue) == 0
Output Redirection
This part is actually pretty tricky. After all, it's an async task queue, so thread contention is inevitable, and there's no way to control each task's output independently. Still, I went ahead and did it—it's usable for small projects (honestly I was just too lazy to port some existing code).
The code is as follows. It likewise uses a queue to implement an output buffer; you can adjust the size yourself. By default it supports outputting to the console, a file, or returning a list.
If you want custom redirected output, you need to implement an output handling function yourself and assign it directly to the custom attribute of the Redirection class.
class Redirection:
def __init__(self, buffer_size=512):
self.buffer = Queue(maxsize=512)
self._console = sys.stdout
# 自定义的输出端
self.custom = None
def write(self, output_stream):
# 加入缓冲区队列
self.buffer.put(output_stream)
def to_console(self):
sys.stdout = self._console
# 出列
while not self.buffer.empty():
print(self.buffer.get())
def to_file(self, file_path):
with open(file_path, 'w+') as f:
sys.stdout = f
while not self.buffer.empty():
print(self.buffer.get())
f.close()
def to_custom(self):
while not self.buffer.empty():
self.custom(self.buffer.get())
def to_list(self):
data = []
while not self.buffer.empty():
data.append(self.buffer.get())
return data
def flush(self):
self.buffer.empty()
def reset(self):
sys.stdout = self._console
Task Class
Not much to say. Define the task class, Task. The code is as follows:
class Task:
def __init__(self, func, callback=None, priority=Priority.MIDDLE, args=(), kwargs={}):
"""
Args:
func: 需要执行的函数
callback: 执行完的回调函数
priority: 优先级
*args:
**kwargs:
"""
self._id = uuid.uuid4().hex
self.function = func
self.callback = callback
self.priority = priority
self.args = args
self.kwargs = kwargs
# 任务运行过程的输出,stdout的输出
self._outputs: Redirection = None
@property
def id(self):
return self._id
@property
def outputs(self) -> Redirection:
return self._outputs
@outputs.setter
def outputs(self, value: Redirection):
self._outputs = value
def run(self):
try:
if self.callback:
# 回调函数原型 callback(task_obj, result)
result = self.callback(self, self.function(*self.args, **self.kwargs))
else:
result = self.function(*self.args, **self.kwargs)
return result
except Exception as e:
if self.callback:
result = self.callback(self, e)
else:
result = e
return result
It's very simple to use, as follows:
def fun1(num1, num2):
print(f'num1={num1}')
print(f'num2={num2}')
return num1 + num2
Task(
func=fun1,
callback=lambda task, result: print(f'task result: {result}'),
)
# 也可以写成这样
Task(
func=lambda num1, num2: num1 + num2,
callback=lambda task, result: print(f'task result: {result}'),
args=[2, 3]
)
Task Queue
Finally, implementing the task queue. It's also simple—just schedule according to task priority.
class TaskQueue:
"""
基于线程的异步任务队列
todo 下次做一个基于进程的队列,充分利用多核CPU性能
"""
def __init__(self, output_redirect=False):
self.queue = PriorityQueue()
self.output_redirect = output_redirect
self._redirect_objs = {}
self._results = {}
def put(self, task: Task):
"""
将task加入任务列表
Args:
task:
Returns:返回task id
"""
self.queue.push(task.priority, task)
return task.id
def get(self):
return self.queue.pop()
def run(self):
while not self.queue.empty:
task = self.get()
# 开启新线程
t = threading.Thread(target=self._task_wrapper, name=task.id, args=[task])
self._log(f'Start thread {task.id}')
t.start()
def get_output(self, task_id: str) -> Redirection:
return self._redirect_objs.get(task_id, None)
def get_result(self, task_id: str):
return self._results.get(task_id, None)
@staticmethod
def _log(msg: str):
"""日志输出接口,可以替换为日志组件"""
print(f'[TaskQueue] {msg}')
def _task_wrapper(self, task: Task):
if self.output_redirect:
if task.id in self._redirect_objs:
redirect_obj = self._redirect_objs[task.id]
else:
redirect_obj = Redirection(2048)
self._redirect_objs[task.id] = redirect_obj
# 重定向输出
sys.stdout = redirect_obj
task.outputs = redirect_obj
result = task.run()
# 恢复默认输出
redirect_obj.reset()
self._log(f'Task finished. {task.id}')
else:
result = task.run()
# 保存结果
self._results[task.id] = result
That's all the project code. It's simple, and there are plenty of shortcomings, but it's barely good enough for small projects~
I'll publish it to pip in a few days. Anyone who needs it can just pip install it. Please follow the blog for updates.
Demo
Here's demo code for using the task queue:
if __name__ == '__main__':
task_queue = TaskQueue(output_redirect=True)
def fun1():
time.sleep(2)
return 1
def fun2():
time.sleep(3)
return 2
task_queue.put(Task(
func=fun1,
callback=lambda task, result: print(f'task1 result: {result}'),
))
task_queue.put(Task(
func=fun2,
callback=lambda task, result: print(f'task2 result: {result}'),
))
def custom_output(msg):
print(f'[custom] {msg}')
def fun3(num1, num2):
print(f'num1={num1}')
print(f'num2={num2}')
return num1 + num2
def callback(task_obj, result):
print(f'task3 result={result}')
output = task_obj.outputs
output.custom = custom_output
output.reset()
output.to_custom()
task_queue.put(Task(func=fun3, callback=callback, args=[2, 3]))
print('task queue run')
task_queue.run()
print('do other things...')
for i in range(0, 100):
print(i * i)
References
- Operator overloading https://blog.csdn.net/goodlixueyong/article/details/52589979
- https://www.open-open.com/code/view/1433410658322
- Celery async task queue https://www.cnblogs.com/StitchSun/p/8552488.html
- https://zhuanlan.zhihu.com/p/37637660
Welcome to Discuss
For questions, please leave a message on the WeChat official account backend. I’ll reply to every message~
- WeChat official account: 画星星高手
- Coding livestream: https://live.bilibili.com/11883038
- Zhihu: https://www.zhihu.com/people/dealiaxy
- Jianshu: https://www.jianshu.com/u/965b95853b9f
_EOF_
Build a Simple Message Queue (Async Task Queue) Yourself: Python Implementation
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
Python踩坑:类与类对象类型参数传递与使用
前言 对初学者来说,Python确实简单好用,毕竟动态类型语言,不用定义就可以拿来用,类型之间随意转换简直不要太方便,因此Python用来写写小脚本,爬虫程序什么的,没什么问题。 不过,一旦用来开发稍微大型一点的项目,例如搭建一个Web应用,就会遇到一些问题,一般缺乏经验的人都会陷入某些坑中。= =... 坑 先说坑,函数参数类型是一坑,类与类的对象这又是一坑。 虽然之前用其他静态类型语言(例如C
2022-05-27 · Python · 约 1 分钟
手写核心价值观编码工具-Python实现
前言 今天是五一劳动节,祝各位无产阶级劳动者节日快乐! 然后来整活分享一些有趣的东西~ 这个小工具是我大学时做着玩的,对于各位接班人来说,12个词的核心价值观这东西,大家都非常熟悉了,这工具可以实现将一段话编码为核心价值观实现加密,同时也能将密文解密出来。 为啥要做这个呢,隔太久不太记得了,~~好像是因为游戏里喷队友老是被屏蔽,灵机一动,要是换成和谐的文字不就不会被屏蔽了吗(误~~ 实现原理 简单
2022-05-01 · Python · 约 1 分钟
使用Python+FFMPEG实现视频分割与合并
前言 日常中偶尔会遇到需要简单剪辑处理视频的场景,以前我可能会拿出PR来剪辑一下,(别跟我说国产那些软件,剪辑完视频强制加上广告片头片尾恶心的一批),但是PR毕竟太重量级,剪个简单的视频都要花不少时间,直到我发现了FFmpeg…… 这个FFmpeg真的是神器,免费开源功能强大,好像很多软件都是基于这个开发的,它本身提供了命令行接口就已经能满足我们日常的简单视频处理需求,我用来简单处理视频分割、合并
2022-05-27 · Python · 约 1 分钟
Python实现给图片加水印功能
前言 最近忙得连轴转,很久没更新博客了,代码倒是没啥写,积累了好些东西,接下来一有时间就来更新吧~ 本文记录使用Python实现给图片添加水印的功能实现过程 先看效果 把公众号的封面作为素材 原图是这样的 codelab 加个水印的效果 temp 实现代码 主要实现是参考GitHub上一个项目的:https://github.com/2Dou/watermarker 用到了Pillow库,使用前请
2022-09-29 · Python · 约 1 分钟
讨论
共 0 条加载评论中…