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

2017-02-14 18:15:41 +08:00
 warcraft1236

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

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

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

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

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

要理解两个概念: 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
2017-02-14 19:17:07 +08:00
线程有三个状态:等待 wait ,就绪 ready 和运行 running 。
gamexg
2017-02-14 20:31:58 +08:00
很多方案,例如:完成端口
yongzhong
2017-02-14 20:37:53 +08:00
itfanr
2017-02-14 20:38:04 +08:00
@czheo 说得好。你得保证执行的任务是可以异步的,不能阻塞,然后就可以随便监听完成事件了。就好比一只手抛多个球,只要球没必要一直用手拿着,就能玩很多。
yongzhong
2017-02-14 20:39:26 +08:00
13 楼第二个链接贴错了,先看这个[聊聊同步、异步、阻塞与非阻塞]

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

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

协程完成 io 调度后, cpu 还可以干别的。(让学霸同学帮你写作业,你自己可以去打 dota 这种需要 cpu 的劳动,学霸同学写完作业向你报告,你给他 100 块让他滚蛋,顺便打电话喊女朋友送饭给你吃)

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/340455

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX