寻找更优雅的任务处理解决方案

时间:2019-08-07 16:35:13

标签: python concurrency python-asyncio

我正在研究任务处理解决方案。任务源是具有数千条记录的SQLlite DB。每个任务都是http请求,因此可能需要几秒钟才能完成。我决定使用asyncio进行处理。示例基于小的任务队列,因此按原样使用它们会占用大量内存,并且会花费大量时间来填充任务列表。 在文档中,看起来像这样


    tasks = []
    for i in range(1,10):
            task = asyncio.create_task(worker(i))
            tasks.append(task)


     await asyncio.gather(tasks)

我要做的是从数据库中逐个读取任务并进行处理,以保持并发限制在MAX_CONCURRENT以内 因此,这是我的肮脏技巧,但我相信对此有一个更优雅的解决方案。

UPD 早上一个小时等于晚上两个小时:) 但是无论如何,我认为信号量的使用会更好,但是我不确定如何在循环中使用它。

import random
import asyncio
import aiohttp
from aiohttp import ClientSession

from sqlitedict import SqliteDict

async def testWorker (id,url, db):
    #placeholder url processing
    await asyncio.sleep(random.randint(1,5))


async def main():

    MAX_CONCURRENT = 5
    db = SqliteDict('./taskdb.sqlite', autocommit=True)

    tasks = set()
    it = db.iteritems()
    while True:
        try:
            id, url = next(it)

            if (len(tasks) < MAX_CONCURRENT):
                task = asyncio.create_task(testTask(id,url, db))
                tasks.add(task)
            else:
                done, pending = await asyncio.wait(tasks,return_when=asyncio.FIRST_COMPLETED)
                tasks = pending

        except StopIteration:              
            break

    done, pending = await asyncio.wait(tasks)

if __name__ == "__main__":
    asyncio.run(main())

1 个答案:

答案 0 :(得分:0)

您发现,最惯用的方法是使用semaphore。在那种情况下,您的循环变得简单得多,因为它不再需要执行MAX_CONCURRENT,而这一切都是由信号量完成的:

async def testWorker(id,url, db, semaphore):
    # async with ensures that no more than MAX_CONCURRENT
    # workers enter the block at the same time
    async with semaphore:
        await asyncio.sleep(random.randint(1,5))

async def main():
    semaphore = asyncio.Semaphore(MAX_CONCURRENT)
    db = SqliteDict('./taskdb.sqlite', autocommit=True)

    async with ClientSession() as session:
        coros = [testTask(id, url, db, semaphore)
                 for id, url in db.iteritems()]
        results = await asyncio.gather(*tasks)

另一种选择是启动固定数量的工人并通过queue与他们进行通信。这稍微复杂一点,但是当任务数量可能巨大或无限制时,这是一个不错的选择。

async def testWorker(db, queue):
    while True:
        id, url = queue.get()
        await asyncio.sleep(random.randint(1,5))
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    workers = [asyncio.create_task(db, queue)]
    db = SqliteDict('./taskdb.sqlite', autocommit=True)

    async with ClientSession() as session:
        for id, url in db.iteritems():
            await queue.put((id, url))
        await queue.join()

    # cancel the workers, which are now sitting idly
    for w in workers:
        w.cancel()
相关问题