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
nightan
V2EX  ›  Python

Python 的协程到底有啥用啊…

  •  
  •   nightan · 2020-03-23 23:11:47 +08:00 via Android · 8140 次点击
    这是一个创建于 1706 天前的主题,其中的信息可能已经有所发展或是发生改变。
    看了好多文章,也自己尝试用了用协程,但是感觉对程序的运行效率并没什么太大的提升…
    我原以为我可以在通过 HTTP 取数据的这个时间内让程序去处理别的逻辑,处理完后再回来处理取回来的数据…
    可能是我太菜了…(捂脸)
    45 条回复    2020-04-13 17:13:51 +08:00
    LokiSharp
        1
    LokiSharp  
       2020-03-23 23:15:41 +08:00
    你得所有都异步非阻塞才行
    ipwx
        2
    ipwx  
       2020-03-23 23:24:17 +08:00
    ClericPy
        3
    ClericPy  
       2020-03-23 23:24:48 +08:00
    全局协程写起来有爽的地方也有不习惯的地方, 爽的基本就是全程非阻塞了, 以前多线程处理的事情全丢给 Future/Task, 整个程序基本不会因为一句代码影响其他代码(写错的情况下还是可能的... 比如错用 time.sleep). 不爽的地方就是程序不能自己识别自己要不要 await 一个结果, 害我为了兼容普通函数和协程的执行结果, 总得

    result = (await result) if isawaitable(result) else xxx

    性能的话, 不开 uvloop 感觉不出太明显差距
    mimzy
        4
    mimzy  
       2020-03-23 23:34:54 +08:00
    用同步的思维写异步程序
    减少锁、上下文切换、线程自身的开销
    mimzy
        5
    mimzy  
       2020-03-23 23:35:07 +08:00   ❤️ 1
    For I/O-bound workloads, there are exactly (only!) two reasons to use async-based concurrency over thread-based concurrency:

    - Asyncio offers a safer alternative to preemptive multitasking (i.e., using threads), thereby avoiding the bugs, race conditions, and other nondeterministic dangers that frequently occur in nontrivial threaded applications.

    - Asyncio offers a simple way to support many thousands of simultaneous socket connections, including being able to handle many long-lived connections for newer technologies like WebSockets, or MQTT for Internet of Things (IoT) applications.

    摘自 Using Asyncio in Python 昨天刚好看了这本书 如果感兴趣且能看到的话可以看下 还是挺不错的 http://shop.oreilly.com/product/0636920320876.do
    zhuangzhuang1988
        6
    zhuangzhuang1988  
       2020-03-24 00:16:24 +08:00
    xingheng
        7
    xingheng  
       2020-03-24 01:27:18 +08:00
    协程的意义在意执行多个没有上下文结果依赖的“不相关”的任务的时候会让这些任务并行执行,同时又不需要担心线程的状态管理,以此达到运行效率的提升。
    但是,有一种情况下协程并不会提升效率,理论上反而会降低效率(因为线程切换的代价)。

    async def run(tasks):
    all_results = []
    for task in tasks:
    result = await execute(task) # the only one await
    all_results.append(result) # append logic result of execute, not task itself

    return all_results

    不是特别好的一个例子。只有一个 await task 的话,后续操作又需要拿到结果才能继续,相当于同步的 join 卡在了 caller 所在的线程,没有异步的意义,所以会更慢。当然,基于上面的例子要优化也是非常简单的,不写了
    sylvos
        8
    sylvos  
       2020-03-24 07:15:39 +08:00 via iPhone
    @xingheng 还请写下优化的代码
    janxin
        9
    janxin  
       2020-03-24 07:54:28 +08:00
    因为没有理解并发并行的含义吧...
    YUX
        10
    YUX  
       2020-03-24 08:20:32 +08:00 via iPhone
    https://hatboy.github.io/2019/02/16/Python 异步编程详解 /

    推荐一篇好文章
    nightan
        11
    nightan  
    OP
       2020-03-24 09:19:54 +08:00
    @xingheng 我好像就是写了个这样的东西(捂脸)
    nightan
        12
    nightan  
    OP
       2020-03-24 09:20:34 +08:00
    @YUX 大佬 404 了这个……
    nightan
        13
    nightan  
    OP
       2020-03-24 09:31:22 +08:00
    @xingheng 大佬,求优化的例子……
    jatsz
        14
    jatsz  
       2020-03-24 09:32:28 +08:00
    协程-主要还是保持状态,像你这种还是需要异步 IO,asyncio 。
    在应用上,主要还是生成器,比如你处理未知网络数据,你可以使用协程去迭代处理。
    https://www.imzjy.com/blog/2015-01-01-coroutine
    itskingname
        15
    itskingname  
       2020-03-24 09:39:35 +08:00   ❤️ 2
    我写了一篇文章来说明你遇到的问题。https://mp.weixin.qq.com/s/spayiLNuTnFVOWWeW-jkwQ

    注意文中为了照顾没有基础的读者,有些概念可能并不十分准确,但是表达意思。
    bnm965321
        16
    bnm965321  
       2020-03-24 10:07:16 +08:00
    当然可以在 HTTP 取数据的时间干其他的事情,就算是做 CPU bound 的事情也可以。如果你从头写一个爬虫,一次多发一些 HTTP request 就知道了。HTTP 堵塞的那些时间,CPU 是可以做很多很多事情的
    smallgoogle
        17
    smallgoogle  
       2020-03-24 10:10:51 +08:00
    反正我知道协程速度快很多就对了。
    freakxx
        19
    freakxx  
       2020-03-24 10:27:52 +08:00
    > 这个时间内让程序去处理别的逻辑,处理完后再回来处理取回来的数据

    这个地方读起来有些怪


    之前是写了一个 chunk + requests_async 用协程处理

    你可以这么理解

    for _ in range(100):
    request


    for _ in range(100):
    request_async


    这两者会有区别。

    但如果
    你只是请求一个并直接使用,不一定有你要的效果。
    vicalloy
        20
    vicalloy  
       2020-03-24 10:28:51 +08:00
    协程是单线程的,因此你必须保证你的所有“阻塞”操作都是异步的,不然对性能没有任何提升。
    比如你用 requests 写爬虫程序。由于 requests 的操作都是阻塞的,用协程不会带来任何的性能提升。
    要想提升性能得把 requests 换成非阻塞的库,如 aiohttp 。
    YUX
        21
    YUX  
       2020-03-24 10:35:56 +08:00
    也可以试试 multiprocessing
    nightan
        22
    nightan  
    OP
       2020-03-24 14:23:23 +08:00
    @YUX 感谢大佬的分享,前后大概看得懂……中间看的懵圈……(捂脸),我得慢慢消化消化……
    我现在写的程序中,实际上有三个函数,分别负责——
    - 1. 从某个数据库读数据,存下来
    - 2. 处理这些数据,然后写到另一个库中,同时这些处理后的数据还要让第三个函数拿到
    - 3. 把处理后的数据通过 HTTP 发送出去
    也许我可以把这三个函数放到三个线程中?其中一个函数发生 I/O 操作的时候,其它函数应该会执行吧……?
    YUX
        23
    YUX  
       2020-03-24 14:31:06 +08:00
    @nightan #22 正如前面多次提到的 要全局都非阻塞 不知道你这三个函数是否都满足要求 推荐一个 非阻塞的 orm https://github.com/tortoise/tortoise-orm
    Orenoid
        24
    Orenoid  
       2020-03-24 15:04:29 +08:00
    推荐一篇文章,https://snarky.ca/how-the-heck-does-async-await-work-in-python-3-5/
    作者从头讲解了 Python 协程的工作原理,并实现了一个简易的事件循环,有助于理解基于 Python 协程的工作原理。
    但是这个说的是原理,目前可能不太适合你看。之后有兴趣可以看下,看完可以再读下 asyncio 库的源码。
    Python 协程本质是依赖 IO 多路复用和 yield 去实现异步的,所以只有网络 IO 才有必要使用协程。
    nightan
        25
    nightan  
    OP
       2020-03-24 15:16:37 +08:00
    @YUX 发 HTTP 请求用的 requests 模块,看起来是阻塞的,刚刚我也试过,await 对于这个模块的调用不生效; 所以即使我用多线程,在处理 HTTP 请求的时候,因为是阻塞的,也一定是要等它执行完是吗?阻塞的 I/O 发生时,python 也不能切换到别的线程咯……
    crackhopper
        26
    crackhopper  
       2020-03-24 16:07:05 +08:00
    你得整体在一个异步框架里,才有意义。比如事件驱动的框架。
    freakxx
        27
    freakxx  
       2020-03-24 17:18:22 +08:00
    xcstream
        28
    xcstream  
       2020-03-24 17:23:07 +08:00
    无锁并发
    nightan
        29
    nightan  
    OP
       2020-03-24 19:21:53 +08:00 via Android
    @freakxx 谢谢
    hehheh
        30
    hehheh  
       2020-03-24 20:13:12 +08:00
    我以前写过一个代理 ip 池,测试 1000 个 IP 。如果是多线程大概需要 2 分钟,如果是协程大概 10 秒。
    keepeye
        31
    keepeye  
       2020-03-24 21:17:28 +08:00
    我原以为我可以在通过 HTTP 取数据的这个时间内让程序去处理别的逻辑,处理完后再回来处理取回来的数据
    ====
    你不会以为协程只能 await 吧? asyncio.ensure_feature 以及 asyncio.gather 了解过么?
    nightan
        32
    nightan  
    OP
       2020-03-24 21:43:11 +08:00
    @keepeye 看了楼上大佬分享的博客,asyncio.ensure_feature 有用到……效果明显……但是要用 requests_async,requests 本身是阻塞的似乎没办法……
    keepeye
        33
    keepeye  
       2020-03-24 21:45:39 +08:00
    @nightan aiohttp aiomysql 等等 https://github.com/aio-libs
    nightan
        34
    nightan  
    OP
       2020-03-24 23:29:01 +08:00 via Android
    @keepeye 谢谢!
    ClericPy
        35
    ClericPy  
       2020-03-25 00:00:13 +08:00
    @nightan #32 本身阻塞的就用 run_in_executor 吧, 丢一个线程让它玩儿去. 多看看 encode 和 aiolibs 里的经典库, 看文档灵光乍现, 看源码茅塞顿开
    black11black
        36
    black11black  
       2020-03-25 00:16:06 +08:00
    > 我原以为我可以在通过 HTTP 取数据的这个时间内让程序去处理别的逻辑,处理完后再回来处理取回来的数据…

    ↑ 异步确实是给你干这个的
    SmartKeyerror
        37
    SmartKeyerror  
       2020-03-25 11:12:51 +08:00
    协程的本质是保存当前函数或者例程的运行状态,并主动让出 CPU 资源,使得和当前函数处于"平级"的函数或者是例程能够得到 CPU 资源,并于原有保存点继续执行,整个过程只有少量的函数运行点恢复操作,没有重量级的线程(进程)上下文切换,所以在同样任务的情况下,协程比线程、进程拥有更高的执行效率。
    Python 的协程,对我个人而言还不如线程好用,宁愿使用 gevent patch,也不愿意使用 asyncio 。协程还是 Golang 用起来更加顺手。
    raymanr
        38
    raymanr  
       2020-03-25 11:39:11 +08:00
    我这两天也在看这个, 完全不能理解 async await 的意义...
    一般函数不也是要等他返回结果吗? 为啥要 await ...
    我不做爬虫不写服务器, 平时只和 numpy scipy pandas 打交道, 是不是异步对我其实没啥用 ?
    MinQ
        39
    MinQ  
       2020-03-25 14:52:33 +08:00
    @raymanr 这种一般都用多线程吧,把数据从原始状态转换成某种结构化形式丢去学习,我用 joblib 就比普通循环快几十条街
    lithbitren
        40
    lithbitren  
       2020-03-25 17:26:19 +08:00
    协程仅使用于类似爬虫或服务这种持续性 io 密集的程序,其他平砍算法可以解决的事情用协程就是增加心智负担。
    jwchen
        41
    jwchen  
       2020-03-25 21:00:22 +08:00
    go 的协程并发模型太漂亮了
    @SmartKeyerror
    jwchen
        42
    jwchen  
       2020-03-25 21:01:42 +08:00
    @raymanr
    一般等待返回的时候,本线程就卡住了,await 协程,会在等待运算结果的过程中自动切换运行线程中的其他协程
    nightan
        43
    nightan  
    OP
       2020-03-25 22:49:52 +08:00 via Android
    @raymanr 我这这几天看下来我能感觉到协程有用但是我现在真的是写不好…我暂时先用多线程和队列来优化代码…http 部分我保持顺序执行就好了,因为我还发现我目标的服务器是按顺序处理请求的,同时多个请求过去反而会慢…
    raymanr
        44
    raymanr  
       2020-03-26 09:17:22 +08:00
    @nightan 我暂时先搁置这个问题了, js 的 promise 我都可以理顺, async await 这种就是一直吃不懂, 始终对于异步的理解还在回调函数上面. 找不到回调函数在哪里就不知道发生了什么
    adamwong
        45
    adamwong  
       2020-04-13 17:13:51 +08:00
    @SmartKeyerror 小龙,看看我发现了什么
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2780 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 08:29 · PVG 16:29 · LAX 00:29 · JFK 03:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.