asyncio多并发任务报错too many file descriptors in select的解决思路

问题描述:

s3程序是一个测试tornado web人脸检测功能的测试函数。在concurrent_reaquest的图片数量较大时会产生报错。

async def request_images(images):
    async with aiohttp.ClientSession(json_serialize=ujson.dumps) as session:
        url = "http://localhost:9999/images"
        payload = {'instances': images}

        async with session.post(url, json=payload) as resp:
            v= await resp.text()
                        print(v)

async def main():
...
  concurrent_requests = (request_images(re_im2) for i in range(10000))
  await asyncio.gather(*concurrent_requests)
...
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

报错信息:

Traceback (most recent call last):
  File "C:/Users/14192/MTCNN-zzk/MTCNN/s3aiosend_test.py", line 58, in 
    loop.run_until_complete(main())
  File "C:\Users\14192\Anaconda3\lib\asyncio\base_events.py", line 454, in run_until_complete
    self.run_forever()
  File "C:\Users\14192\Anaconda3\lib\asyncio\base_events.py", line 421, in run_forever
    self._run_once()
  File "C:\Users\14192\Anaconda3\lib\asyncio\base_events.py", line 1389, in _run_once
    event_list = self._selector.select(timeout)
  File "C:\Users\14192\Anaconda3\lib\selectors.py", line 323, in select
    r, w, _ = self._select(self._readers, self._writers, [], timeout)
  File "C:\Users\14192\Anaconda3\lib\selectors.py", line 314, in _select
    r, w, x = select.select(r, w, w, timeout)
ValueError: too many file descriptors in select()

代码分析

从代码来看,异步方法main中,concurrent_requests是一个列表生成器,里面的每个对象都是一次tornado的服务调用返回结果。列表生成器被函数asyncio.gather调用,返回一个Awaitable对象。这里的await关键字是配合函数async关键字使用的,表示挂起自身的协程,并等待另一个协程完成直到返回结果。

main函数被定义成一个协程对象,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。因此,在代码的最后使用了loop = asyncio.get_event_loop() loop.run_until_complete(main())这样两行代码,启动了一个事件循环,并将main函数这个协程注册到事件循环中,启动事件循环。

报错信息也来自事件循环的run_until_complete方法。

可以看到只有被注册到事件循环loop中的async main方法会被当成协程task来处理。而代码中将main方法整个看成一个协程,这个协程内部的代码出现了await,也就是当这个函数出现阻塞的时候,这个协程就会让出控制权,以便loop调用其他的协程。而我们希望实现的是在main函数内部,面对多个tornado web查询时,可以将多个tornado查询看作是不同的协程。而不是把main看成一个协程,在main中遇到await把main程序给挂起,因为这里只有一个协程,因此这个不是主要问题,只是需要美化的部分。

代码主要问题在于asyncio内部使用了select方法,select方法对打开的文件字符长度有最大的限制。一次性将太多的需要web调用函数作为任务时,会导致List过大,报错。并发的思想没有错,这里使用回调接口会比较合适。

解决思路

我们希望的是,对于tornado的服务调用是并行进行的,使用回调接口的实现并发的tornado服务访问。

因此首先将对tornado的访问函数写成支持回调的方式

async def request_images(images):
    async with aiohttp.ClientSession(json_serialize=ujson.dumps) as session:
        url = "http://localhost:9999/images"
        payload = {'instances': images}

        async with session.post(url, json=payload) as resp:
            return await resp.text()

def post_deal(content):
    print(content)

将loop事件循环放入main函数中,实现对每次访问tornado服务的异步调用。

def main():
    face_img = cv2.imread(im_name)
    re_im = face_img
    re_im2 = load_image(im_name)
    import time
    start = time.time()
    loop = asyncio.get_event_loop()
    # concurrent_requests = [asyncio.ensure_future(request_images(re_im2)) for i in range(10000)]
    for i in range(10000):
        concurrent_request = asyncio.ensure_future(request_images(re_im2))
        concurrent_request.add_done_callback(post_deal)
        loop.run_until_complete(concurrent_request)
    end = time.time() - start
    print(end)

if __name__ == '__main__':
    main()

这样代码的问题就通过异步回调解决了。

参考文章
python的重要模块-asynic
asyncio 异步爬虫

你可能感兴趣的:(asyncio多并发任务报错too many file descriptors in select的解决思路)