V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
推荐学习书目
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
daiqiangbudainiu
V2EX  ›  Python

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

  •  
  •   daiqiangbudainiu · Feb 14, 2017 · 5949 views
    This topic created in 3372 days ago, the information mentioned may be changed or developed.

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

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

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

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

    29 replies    2017-02-16 10:09:13 +08:00
    skydiver
        1
    skydiver  
       Feb 14, 2017
    不是说了协程了么……
    daiqiangbudainiu
        2
    daiqiangbudainiu  
    OP
       Feb 14, 2017
    @skydiver 就是协程是怎么做到并发的呢?
    TKKONE
        3
    TKKONE  
    PRO
       Feb 14, 2017 via Android
    异步吧,我也不了解
    neoblackcap
        4
    neoblackcap  
       Feb 14, 2017
    @warcraft1236 IO 操作的时候就挂起啊,调度器去调度执行另外一个协程
    innoink
        5
    innoink  
       Feb 14, 2017
    你这问题就像单核 cpu 怎么支持多任务一样
    需要调度器
    daiqiangbudainiu
        6
    daiqiangbudainiu  
    OP
       Feb 14, 2017
    @neoblackcap 那像例子中,第一个网络请求,挂起了,执行第二个网络请求,不就等于第一个网络请求没有再运行了吗?怎么会是并发呢?
    neoblackcap
        7
    neoblackcap  
       Feb 14, 2017
    @warcraft1236 你应该去了解什么叫并发,什么叫并行
    fds
        8
    fds  
       Feb 14, 2017
    @warcraft1236 操作系统会继续处理你的请求
    dwood
        9
    dwood  
       Feb 14, 2017 via Android
    因为 io 是异步的
    czheo
        10
    czheo  
       Feb 14, 2017   ❤️ 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  
       Feb 14, 2017 via iPhone
    线程有三个状态:等待 wait ,就绪 ready 和运行 running 。
    gamexg
        12
    gamexg  
       Feb 14, 2017 via Android
    很多方案,例如:完成端口
    itfanr
        14
    itfanr  
       Feb 14, 2017 via Android
    @czheo 说得好。你得保证执行的任务是可以异步的,不能阻塞,然后就可以随便监听完成事件了。就好比一只手抛多个球,只要球没必要一直用手拿着,就能玩很多。
    yongzhong
        15
    yongzhong  
       Feb 14, 2017   ❤️ 2
    13 楼第二个链接贴错了,先看这个[聊聊同步、异步、阻塞与非阻塞]

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

    在多线程的思路中,每个线程处理一个连接,连接没有数据的时候,就阻塞等待,有数据的线程运行。
    在异步的思路中,一个线程同时等待多个连接的数据,哪个连接先来了数据就先处理哪个连接,处理完马上回到阻塞状态。
    系统调用 poll 、 select 、 epoll ( linux ) 等提供一个线程可以同时等待多个连接的机制。
    因为网络编程中,等待网络数据是耗时最多的状态,所以使用异步的方法效率很高。
    nicevar
        17
    nicevar  
       Feb 14, 2017
    时间片
    czheo
        18
    czheo  
       Feb 14, 2017
    稍微看了下 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  
       Feb 14, 2017
    我刚开始也有这个困惑
    https://pymotw.com/2/select/
    PythonAnswer
        20
    PythonAnswer  
       Feb 14, 2017
    cpu 计算协程无效。跑满 100%就只能干瞪眼了。

    协程完成 io 调度后, cpu 还可以干别的。(让学霸同学帮你写作业,你自己可以去打 dota 这种需要 cpu 的劳动,学霸同学写完作业向你报告,你给他 100 块让他滚蛋,顺便打电话喊女朋友送饭给你吃)
    araraloren
        21
    araraloren  
       Feb 15, 2017
    那不知道楼主是否知道并发的意思呢,如果明白楼上的各位回答很清楚了。。
    whx20202
        22
    whx20202  
       Feb 15, 2017
    daiqiangbudainiu
        23
    daiqiangbudainiu  
    OP
       Feb 15, 2017
    @yongzhong 感谢
    hfpeng01
        24
    hfpeng01  
       Feb 15, 2017
    如果没有任务阻塞,那么在单处理器上使用并发就没有任何意义。有阻塞的话,就是切分 CPU 时间片, cpu 轮流给每个任务分配占用时间。
    WangYanjie
        25
    WangYanjie  
       Feb 15, 2017
    msg7086
        26
    msg7086  
       Feb 15, 2017
    非阻塞就可以实现单个线程并发了。
    软件的线程本身就是非阻塞的,所以一个 CPU 线程可以运行几百几千个软件线程。
    同理如果你操作也是非阻塞的,那一个软件线程也就可以运行几百几千个操作了。
    wwqgtxx
        27
    wwqgtxx  
       Feb 15, 2017
    @WangYanjie 现在应该是 libev 了,从 gevent1.0 开始就不用 libevent 了
    itfanr
        28
    itfanr  
       Feb 15, 2017 via Android
    @yongzhong 哈哈
    WangYanjie
        29
    WangYanjie  
       Feb 16, 2017
    @wwqgtxx 嗯,没注意,提留在 0.9.x 太久了。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5984 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 114ms · UTC 01:48 · PVG 09:48 · LAX 18:48 · JFK 21:48
    ♥ Do have faith in what you're doing.