V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
liu826250634
V2EX  ›  Python

关于 Python 的 asyncio 协程的问题。

  •  
  •   liu826250634 · 2020-05-25 17:55:26 +08:00 · 3100 次点击
    这是一个创建于 1429 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先说需求: 我需要处理读取多个视频, 每个视频间隔一定时间取帧进行图像识别。下面是提取视频的代码, 一开始我是使用多进程实现的。后面想着读取视频应该是磁盘 IO, 是否使用协程合适?查资料说不是原生异步需要使用线程池维护, 但是写下来,发现也没关协程啥事啊?是不是我写的有问题。正确写法应该怎么样,求各位大佬解答。

    import cv2
    import asyncio
    import os
    import numpy as np
    import time
    
    def read(cap):
        ret, frame = cap.read()
        return ret, frame
    
    def put_frame(queue, video_path,interval=50):
        '''
        queue: 队列
        video_path: 视频路径
        interval: 间隔多少帧取一次图片
        '''
        num = 0
        img_list = []
        cap = cv2.VideoCapture(video_path)
        start = time.time()
        while cap.isOpened():
            cap.set(cv2.CAP_PROP_POS_FRAMES, num*interval)
            ret, frame = read(cap)
            if ret:
                custom_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                cv2.imshow("{0}".format(video_path), frame)
                cv2.waitKey(24)
                custom_image = cv2.resize(
                custom_image, (416, 416), interpolation=cv2.INTER_NEAREST)
                custom_image = custom_image.transpose(2, 0, 1)
                img_list.append(custom_image)
                num += 1
            else:
                arr = np.concatenate(img_list, axis=0)
                # arr = np.ascontiguousarray(arr.flat, dtype=np.float32) / 255.0
                queue.put(arr)
                print("--------------, {0}提取视频完成, 用时:{1}", video_path, (time.time()-start))
                break
    
    
    video_dir = './video'
    loop = asyncio.get_event_loop()
    tasks = []
    queue = asyncio.Queue(loop=loop, maxsize=10)
    for video in os.listdir(video_dir):
        # await asyncio.sleep(0)
        video_path = os.path.join(video_dir, video)
        task = loop.run_in_executor(None, put_frame, queue, video_path)
        tasks.append(task)
    
    loop.run_until_complete(asyncio.wait(tasks))
    
    
    
    # 他还会出现这个警告:
    C:/Users/rookie/Desktop/20200512/demo3.py:36: RuntimeWarning: coroutine 'Queue.put' was never awaited
      queue.put(arr)
    RuntimeWarning: Enable tracemalloc to get the object allocation traceback
    
    16 条回复    2020-05-27 15:59:05 +08:00
    yuchenyang1994
        1
    yuchenyang1994  
       2020-05-25 17:59:37 +08:00
    你 queue.put 没加 await await queue.push() 如果不想加,可以用 queue.put_no_wait, 还有函数没加 async 放弃吧,没啥意义这个用协程做
    superlzy
        2
    superlzy  
       2020-05-25 18:02:55 +08:00
    await xxx.put(x)
    或者
    xxx.put_nowait(x)
    oahebky
        3
    oahebky  
       2020-05-25 18:05:26 +08:00
    asyncio 在 programmer 手上一般是最上层调用。
    需要较低层库支持(如此处的 CV2,OpenCV 的 asyncio 实现库)。

    所以粗略来看,你这个是实现不了 asyncio 的。

    ======

    比如 cv2.VideoCapture(video_path)
    支持 asyncio 应该像 cap = await aiocv2.VideoCapture(path)
    用起来长得应该就像上面这样,才是真的用 asyncio 。
    superrichman
        4
    superrichman  
       2020-05-25 18:11:16 +08:00 via iPhone   ❤️ 1
    从你的代码来看,你这个场景是计算密集型,用异步 io 收益不大。你直接用多进程把 cpu 跑满就好。
    liu826250634
        5
    liu826250634  
    OP
       2020-05-25 18:15:32 +08:00
    @superrichman 使用协程收益确实不大, 但是我想了解下协程正确写法应该是怎么样的。。。
    liu826250634
        6
    liu826250634  
    OP
       2020-05-25 18:17:23 +08:00
    @oahebky 我看着看着文档, 怎么感觉 asyncio 使用起来场景限制很大,一般除了用在网络 IO, 还能用在什么地方呢?
    如果用网络 IO, 和多线程其实也没什么区别啊, 反正也是卡在网络上。
    superrichman
        7
    superrichman  
       2020-05-25 18:26:18 +08:00 via iPhone
    @liu826250634 一处异步,处处异步。opencv2 没有做异步的支持,那处理这步就没办法异步了。
    Vegetable
        8
    Vegetable  
       2020-05-25 18:31:38 +08:00
    看到视频两个字后边都没看,视频图像这些领域,基本都是计算密集,协程说破天去也是单进程,对于计算密集的任务还是要利用好多核级的算能力。
    oahebky
        9
    oahebky  
       2020-05-25 18:44:07 +08:00 via Android
    @liu826250634

    有些语言设计之初就是异步的,所以它们的底层和它们的库在实现的时候也就是异步的;
    因此对于这种语言,它们的使用场景本身就没有 Python 广泛,所以在这些语言的领域里面用起来异步是相对比较“自然”地。

    不过 Python 应用场景很广泛,所以想要在不同的场景用异步,必须要不同场景的库支持才可以。
    不过,即使不支持,通过消息队列把软件拆分或者其它手段也还是可以做到一整个大软件,在适合用异步的地方使用异步。

    磁盘 IO 和网络 IO 都可以用异步,所以未来 Python 的异步库更加健壮之后,应该在 web server 方面可以做到大范围的异步。
    比如说想象一个用户请求到达了 server,然后执行到 SQL 查询,这时候慢 IO 交出该线程控制权,就可以 handler 其它传入的 socket - 阻塞 IO 连接请求、资源请求;在 SQL 查询的零点几秒时间系统是完全有时间建立起一个新的 socket fd,或者 CPU 执行一部分运算或者发起一次新的慢 IO 的。
    CPU 的执行速度要比人类想象的快得多得多。

    而 CPU 密集形运算,比如处理在内存中的图像矩阵,那么在数值计算这方面就不应该玩异步上面考虑。
    因此什么时候使用异步自然是要分具体应用的。
    lttzzlll
        10
    lttzzlll  
       2020-05-25 18:51:14 +08:00
    如果一定要用 asyncio,需要把耗时的操作放到一个单独的进程中。

    ```Python
    import os
    import time
    import asyncio
    from concurrent.futures.process import ProcessPoolExecutor

    pool = ProcessPoolExecutor(max_workers=os.cpu_count())


    def add(a, b):
    """
    耗时操作
    """
    time.sleep(3)
    return a + b

    async def main():
    loop =asyncio.get_running_loop()
    r = await loop.run_in_executor(pool, add, 1, 2)
    print(r)

    asyncio.run(main())
    ```
    Latin
        11
    Latin  
       2020-05-25 18:54:11 +08:00
    Jat001
        12
    Jat001  
       2020-05-25 19:05:11 +08:00 via iPhone
    你都用 opencv 了还考虑性能问题? cv2.waitKey 的用时一定比你指定的时间长,cv2.imshow 并没有开始渲染,到 cv2.waitKey 的时候才渲染图片。就算你拿 opencv 什么都不干,只是一帧帧播放,也会比原视频慢。
    liu826250634
        13
    liu826250634  
    OP
       2020-05-25 19:29:59 +08:00
    @Jat001 这个我只是用来看看的而已。
    Jat001
        14
    Jat001  
       2020-05-25 20:32:02 +08:00
    @liu826250634 opencv 也就能拿来做做 demo 了,生产环境请用 vpf
    liu826250634
        15
    liu826250634  
    OP
       2020-05-26 10:41:43 +08:00
    @Jat001 如果没有 GPU,VPF 还可以使用么?
    sujin190
        16
    sujin190  
       2020-05-27 15:59:05 +08:00
    磁盘 IO 异步话似乎完全没啥必要吧,现在 ssd 的话本来就很快,而异步增加逻辑复杂大多数情况消耗的资源更多吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1013 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 20:04 · PVG 04:04 · LAX 13:04 · JFK 16:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.