廖雪峰大大的教程中,有这么一句
3 个网络操作是并发执行的,而且结束顺序不同,但只有一个线程
我不理解,为什么单线程就能够并发 3 个网络请求呢
1
skydiver 2017-02-14 18:25:22 +08:00
不是说了协程了么……
|
2
warcraft1236 OP @skydiver 就是协程是怎么做到并发的呢?
|
3
tumbzzc 2017-02-14 18:29:45 +08:00 via Android
异步吧,我也不了解
|
4
neoblackcap 2017-02-14 18:31:56 +08:00
@warcraft1236 IO 操作的时候就挂起啊,调度器去调度执行另外一个协程
|
5
innoink 2017-02-14 18:33:38 +08:00
你这问题就像单核 cpu 怎么支持多任务一样
需要调度器 |
6
warcraft1236 OP @neoblackcap 那像例子中,第一个网络请求,挂起了,执行第二个网络请求,不就等于第一个网络请求没有再运行了吗?怎么会是并发呢?
|
7
neoblackcap 2017-02-14 18:41:21 +08:00
@warcraft1236 你应该去了解什么叫并发,什么叫并行
|
8
fds 2017-02-14 18:51:24 +08:00
@warcraft1236 操作系统会继续处理你的请求
|
9
dwood 2017-02-14 19:06:21 +08:00 via Android
因为 io 是异步的
|
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 结束了。这样你就知道什么时候恢复你的程序执行了。 当然这么简单说一说,如果不了解操作系统底层的一些知识,感觉还是挺难理解的。 我猜是这么回事,有不对的请指出。 |
11
Allianzcortex 2017-02-14 19:17:07 +08:00 via iPhone
线程有三个状态:等待 wait ,就绪 ready 和运行 running 。
|
12
gamexg 2017-02-14 20:31:58 +08:00 via Android
很多方案,例如:完成端口
|
14
itfanr 2017-02-14 20:38:04 +08:00 via Android
@czheo 说得好。你得保证执行的任务是可以异步的,不能阻塞,然后就可以随便监听完成事件了。就好比一只手抛多个球,只要球没必要一直用手拿着,就能玩很多。
|
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 |
16
wizardoz 2017-02-14 20:39:58 +08:00
只有一个 cpu ,多线程 /多进程是怎么做到并发执行的呢?
开个玩笑…… 在多线程的思路中,每个线程处理一个连接,连接没有数据的时候,就阻塞等待,有数据的线程运行。 在异步的思路中,一个线程同时等待多个连接的数据,哪个连接先来了数据就先处理哪个连接,处理完马上回到阻塞状态。 系统调用 poll 、 select 、 epoll ( linux ) 等提供一个线程可以同时等待多个连接的机制。 因为网络编程中,等待网络数据是耗时最多的状态,所以使用异步的方法效率很高。 |
17
nicevar 2017-02-14 20:59:04 +08:00
时间片
|
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 |
19
quxw 2017-02-14 22:02:52 +08:00
我刚开始也有这个困惑
https://pymotw.com/2/select/ |
20
PythonAnswer 2017-02-14 22:41:09 +08:00
cpu 计算协程无效。跑满 100%就只能干瞪眼了。
协程完成 io 调度后, cpu 还可以干别的。(让学霸同学帮你写作业,你自己可以去打 dota 这种需要 cpu 的劳动,学霸同学写完作业向你报告,你给他 100 块让他滚蛋,顺便打电话喊女朋友送饭给你吃) |
21
araraloren 2017-02-15 08:25:56 +08:00
那不知道楼主是否知道并发的意思呢,如果明白楼上的各位回答很清楚了。。
|
22
whx20202 2017-02-15 10:00:57 +08:00
|
23
warcraft1236 OP @yongzhong 感谢
|
24
hfpeng01 2017-02-15 11:33:07 +08:00
如果没有任务阻塞,那么在单处理器上使用并发就没有任何意义。有阻塞的话,就是切分 CPU 时间片, cpu 轮流给每个任务分配占用时间。
|
25
WangYanjie 2017-02-15 12:21:44 +08:00
|
26
msg7086 2017-02-15 14:55:52 +08:00
非阻塞就可以实现单个线程并发了。
软件的线程本身就是非阻塞的,所以一个 CPU 线程可以运行几百几千个软件线程。 同理如果你操作也是非阻塞的,那一个软件线程也就可以运行几百几千个操作了。 |
27
wwqgtxx 2017-02-15 16:19:37 +08:00
@WangYanjie 现在应该是 libev 了,从 gevent1.0 开始就不用 libevent 了
|
29
WangYanjie 2017-02-16 10:09:13 +08:00
@wwqgtxx 嗯,没注意,提留在 0.9.x 太久了。
|