图片来源:FGC/Shutterstock
以下代码实现了在单进程下,主线程不断产生数据,并传入子线程中;由子线程中的事件循环异步地将数据放入队列中,再异步地消费掉,消费完后释放所有相关资源;另外每个资源包都配有异步倒计时器,一旦倒计时完成,强制释放资源
import asyncio
from threading import Thread
import time
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
def create(timestamp, worker_pool, worker_loop):
worker_pool[timestamp] = {}
consumer_task = asyncio.run_coroutine_threadsafe(consumer(worker_pool, timestamp), worker_loop)
timeout_task = asyncio.run_coroutine_threadsafe(timeout(worker_pool, timestamp), worker_loop)
worker_pool[timestamp]['consumer'] = consumer_task
worker_pool[timestamp]['timeout'] = timeout_task
asyncio.run_coroutine_threadsafe(producer(worker_pool, timestamp), worker_loop)
async def producer(worker_pool, timestamp):
await worker_pool[timestamp]['mq'].put(timestamp)
async def consumer(worker_pool, timestamp):
worker_pool[timestamp]['mq'] = asyncio.Queue()
while True:
await worker_pool[timestamp]['mq'].get()
worker_pool[timestamp]['timeout'].cancel()
worker_pool.pop(timestamp)
break
async def timeout(worker_pool, timestamp):
await asyncio.sleep(10)
worker_pool[timestamp]['consumer'].cancel()
worker_pool.pop(timestamp)
if __name__ == "__main__":
worker_loop = asyncio.new_event_loop()
loop_thread = Thread(target=start_loop, args=(worker_loop,))
loop_thread.daemon = True
loop_thread.start()
worker_pool = {}
count = 0
start = time.time()
while True:
if time.time() - start > 1:
start = time.time()
print(count)
count = 0
count += 1
timestamp = str(time.time())
create(timestamp, worker_pool, worker_loop)
print(f'-------- {len(worker_pool)}', end='\r', flush=True)
之所以要使用多线程
- 是因为要将数据产生的过程与其他过程隔离,从而不会因为其他过程的阻塞而影响数据产生过程,而实现这种隔离可以用多线程,也可以用多进程,而不能用协程,因为协程是用户态的线程,其调度不受操作系统控制。
- 可以实现在事件循环中动态添加任务
所以主线程的任务包括:
- 以sleep_time时间间隔循环产生时间戳数据timestamp
- 以时间戳为键,在worker_pool中创建字典数据,资源包括消费者任务对象、倒计时任务对象、数据队列对象
- 将数据传递给子线程的事件循环中,创建生产者任务、消费者任务、倒计时任务
子线程的任务包括:
- 执行事件循环
- 接受数据,执行生产者任务、消费者任务、倒计时任务
- 生产者任务:异步方式向队列传入数据
- 消费者任务:异步方式从队列取出数据、释放相关资源
- 倒计时任务:异步方式倒计时,倒计时完成强制释放相关资源
下图可见即使每秒产生2000+条数据,也能消费得过来,没有产生大规模数据堆积
(由于Mac上的Docker无法做到像Linux Docker一样完全使用Linux内核,因而速度会慢一些,在Mac环境下测试每秒能产生5000+条)