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

Gevent 是怎么在单线程中实现并发的呢?

  •  
  •   warcraft1236 · 2017-02-14 18:15:41 +08:00 · 5027 次点击
    这是一个创建于 2840 天前的主题,其中的信息可能已经有所发展或是发生改变。

    教程链接: http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001407503089986d175822da68d4d6685fbe849a0e0ca35000

    廖雪峰大大的教程中,有这么一句

    3 个网络操作是并发执行的,而且结束顺序不同,但只有一个线程
    

    我不理解,为什么单线程就能够并发 3 个网络请求呢

    29 条回复    2017-02-16 10:09:13 +08:00
    skydiver
        1
    skydiver  
       2017-02-14 18:25:22 +08:00
    不是说了协程了么……
    warcraft1236
        2
    warcraft1236  
    OP
       2017-02-14 18:28:34 +08:00
    @skydiver 就是协程是怎么做到并发的呢?
    tumbzzc
        3
    tumbzzc  
       2017-02-14 18:29:45 +08:00 via Android
    异步吧,我也不了解
    neoblackcap
        4
    neoblackcap  
       2017-02-14 18:31:56 +08:00
    @warcraft1236 IO 操作的时候就挂起啊,调度器去调度执行另外一个协程
    innoink
        5
    innoink  
       2017-02-14 18:33:38 +08:00
    你这问题就像单核 cpu 怎么支持多任务一样
    需要调度器
    warcraft1236
        6
    warcraft1236  
    OP
       2017-02-14 18:35:15 +08:00
    @neoblackcap 那像例子中,第一个网络请求,挂起了,执行第二个网络请求,不就等于第一个网络请求没有再运行了吗?怎么会是并发呢?
    neoblackcap
        7
    neoblackcap  
       2017-02-14 18:41:21 +08:00
    @warcraft1236 你应该去了解什么叫并发,什么叫并行
    fds
        8
    fds  
       2017-02-14 18:51:24 +08:00
    @warcraft1236 操作系统会继续处理你的请求
    dwood
        9
    dwood  
       2017-02-14 19:06:21 +08:00 via Android
    因为 io 是异步的
    czheo
        10
    czheo  
       2017-02-14 19:09:45 +08:00   ❤️ 1
    这个问题问题很好。虽然我没有自己实现过,但我理解大概是这样的。

    要理解两个概念: 1. 协程( coroutine ) 2.事件驱动( event-driven )。

    1. coroutine (类似于 python 里面的 yield 语法),是把一个 function 执行过程暂停,将其 stack 状态保存,当你需要的时候恢复 stack 状态,从暂停的地方继续执行。
    通过这样,你可以自由的控制程序的暂停和恢复。在你的例子里面其实是,把每个 function 执行到 io 操作时都暂停,等你需要的时候( io 返回结果的时候)恢复执行。
    但问题是,什么时候你知道 io 返回结果了呢?就需要另外一个机制通知你。

    2. event-driven 就是通知你 io 操作完成的机制。基本想法就是 io 操作发生时,你把 io 的 file descriptor 存到一个 poll 里面,操作系统会监控这个 poll 。当其中有 io 结束的时候,操作系统会给你的程序发一个 signal ( event ),告诉你哪一个 io 结束了。这样你就知道什么时候恢复你的程序执行了。

    当然这么简单说一说,如果不了解操作系统底层的一些知识,感觉还是挺难理解的。
    我猜是这么回事,有不对的请指出。
    Allianzcortex
        11
    Allianzcortex  
       2017-02-14 19:17:07 +08:00 via iPhone
    线程有三个状态:等待 wait ,就绪 ready 和运行 running 。
    gamexg
        12
    gamexg  
       2017-02-14 20:31:58 +08:00 via Android
    很多方案,例如:完成端口
    itfanr
        14
    itfanr  
       2017-02-14 20:38:04 +08:00 via Android
    @czheo 说得好。你得保证执行的任务是可以异步的,不能阻塞,然后就可以随便监听完成事件了。就好比一只手抛多个球,只要球没必要一直用手拿着,就能玩很多。
    yongzhong
        15
    yongzhong  
       2017-02-14 20:39:26 +08:00   ❤️ 2
    13 楼第二个链接贴错了,先看这个[聊聊同步、异步、阻塞与非阻塞]

    http://mp.weixin.qq.com/s?__biz=MzA4MjEyNTA5Mw==&mid=2652563596&idx=1&sn=ea234e176e36775effa9634f105ecf6d&scene=21#wechat_redirect
    wizardoz
        16
    wizardoz  
       2017-02-14 20:39:58 +08:00
    只有一个 cpu ,多线程 /多进程是怎么做到并发执行的呢?
    开个玩笑……

    在多线程的思路中,每个线程处理一个连接,连接没有数据的时候,就阻塞等待,有数据的线程运行。
    在异步的思路中,一个线程同时等待多个连接的数据,哪个连接先来了数据就先处理哪个连接,处理完马上回到阻塞状态。
    系统调用 poll 、 select 、 epoll ( linux ) 等提供一个线程可以同时等待多个连接的机制。
    因为网络编程中,等待网络数据是耗时最多的状态,所以使用异步的方法效率很高。
    nicevar
        17
    nicevar  
       2017-02-14 20:59:04 +08:00
    时间片
    czheo
        18
    czheo  
       2017-02-14 21:50:32 +08:00
    稍微看了下 gevent 的具体实现。基本思路是把 python 标准库里面的所有 io 操作都变成 event driven 。好大的工程。。。

    首先是 monkey.patch_all 实际是把所有的 io 模块都打了补丁,具体可以看 patch_module 那个函数,用 gevent.{os, socket, sys...}自定义的操作替代了原生标准模块的操作。
    https://github.com/gevent/gevent/blob/master/src/gevent/monkey.py#L583
    https://github.com/gevent/gevent/blob/master/src/gevent/monkey.py#L152

    比如 gevent.socket 这个模块其实是覆盖了原生的 socket 模块,关键操作在 socket._wait 这个函数,把 io event 注册到 gevent.hub 里面。
    https://github.com/gevent/gevent/blob/master/src/gevent/_socket3.py#L306
    https://github.com/gevent/gevent/blob/master/src/gevent/_socket3.py#L157

    gevent.hub 是对 eventloop 的实现。
    https://github.com/gevent/gevent/blob/master/src/gevent/hub.py
    quxw
        19
    quxw  
       2017-02-14 22:02:52 +08:00
    我刚开始也有这个困惑
    https://pymotw.com/2/select/
    PythonAnswer
        20
    PythonAnswer  
       2017-02-14 22:41:09 +08:00
    cpu 计算协程无效。跑满 100%就只能干瞪眼了。

    协程完成 io 调度后, cpu 还可以干别的。(让学霸同学帮你写作业,你自己可以去打 dota 这种需要 cpu 的劳动,学霸同学写完作业向你报告,你给他 100 块让他滚蛋,顺便打电话喊女朋友送饭给你吃)
    araraloren
        21
    araraloren  
       2017-02-15 08:25:56 +08:00
    那不知道楼主是否知道并发的意思呢,如果明白楼上的各位回答很清楚了。。
    whx20202
        22
    whx20202  
       2017-02-15 10:00:57 +08:00
    warcraft1236
        23
    warcraft1236  
    OP
       2017-02-15 10:13:44 +08:00
    @yongzhong 感谢
    hfpeng01
        24
    hfpeng01  
       2017-02-15 11:33:07 +08:00
    如果没有任务阻塞,那么在单处理器上使用并发就没有任何意义。有阻塞的话,就是切分 CPU 时间片, cpu 轮流给每个任务分配占用时间。
    WangYanjie
        25
    WangYanjie  
       2017-02-15 12:21:44 +08:00
    msg7086
        26
    msg7086  
       2017-02-15 14:55:52 +08:00
    非阻塞就可以实现单个线程并发了。
    软件的线程本身就是非阻塞的,所以一个 CPU 线程可以运行几百几千个软件线程。
    同理如果你操作也是非阻塞的,那一个软件线程也就可以运行几百几千个操作了。
    wwqgtxx
        27
    wwqgtxx  
       2017-02-15 16:19:37 +08:00
    @WangYanjie 现在应该是 libev 了,从 gevent1.0 开始就不用 libevent 了
    itfanr
        28
    itfanr  
       2017-02-15 18:59:10 +08:00 via Android
    @yongzhong 哈哈
    WangYanjie
        29
    WangYanjie  
       2017-02-16 10:09:13 +08:00
    @wwqgtxx 嗯,没注意,提留在 0.9.x 太久了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5675 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 41ms · UTC 01:44 · PVG 09:44 · LAX 17:44 · JFK 20:44
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.