Python 有 thread,为什么还要 asyncio 呢?

偶尔会用到 thread 做异步任务,没想到什么时候用 asyncio,这两个异步有什么差别吗,理解的不是很清楚
Python 有 thread,为什么还要 asyncio 呢?

36 回复

是两种异步方案


简单说,thread是操作系统层面的并发,asyncio是单线程内的并发,两者解决不同场景的问题。

thread适合CPU密集型任务,能利用多核。但线程切换开销大,且Python有GIL限制,多线程在I/O等待时依然会被阻塞。

asyncio的核心是事件循环+协程,特别适合I/O密集型场景(网络请求、文件读写)。它在单个线程内通过await挂起遇到I/O的协程,让事件循环去执行其他协程,避免了线程切换的开销。代码看起来是同步的,但实际是异步执行。

举个例子,你要爬100个网页:

  • 用多线程:开100个线程,操作系统调度,内存开销大。
  • 用asyncio:单线程内发起100个请求,哪个先返回就先处理哪个,效率更高。

但asyncio也有局限:一个协程阻塞会拖慢整个事件循环,CPU密集型任务还是得用多进程。

总结:I/O多用asyncio,计算多用thread或多进程。

有 urllib,为什么还要 urllib2 呢?

asyncio 是基于 yield 的
thread 受限于 gil, 用处不大

线程和协程

多进程,线程,协程,异步

这四个概念先懂了,我们再来聊聊这个问题。

asyncio: This module provides infrastructure for writing [single-threaded concurrent code using coroutines, multiplexing I/O] access over sockets and other resources, running network clients and servers, and other related primitives.

thread: This module provides low-level primitives for working with [multiple threads (also called light-weight processes or tasks) ] — multiple threads of control sharing their global data space. For synchronization, simple locks (also called mutexes or binary semaphores) are provided.

[] 里的含义,了解下相信你就明白了。

虽然没写过 Python 但是还是来回答下…是来降低上下文切换所带来的性能损失.

  1. 虽然大家(包括楼上的 )都说 thread 受限于 GIL,但是那是对 CPU bounded tasks 而言,大多情况下 thread 还是足以应付 IO bounded tasks。
    2. 即使如此,应付 IO bounded tasks 时,thread 也存在两个问题,一是 thread overhead 开销较大,二是受到系统 max thread number 的限制。基于 coroutine 的 asyncio 有助于克服这两个问题。
    3. 然而 asyncio 复杂的 API 还是被人诟病: https://twitter.com/mitsuhiko/status/792441114561220609
    最近几年 Python3.4 以后的几个新特性都是围绕基于 coroutine 的 concurrency ( async generator in 3.6, async/await in 3.5, asyncio in 3.4 ),各个 Python 的会议也都会有关于 concurrency 话题的 talk,说明社区还在努力探索。

#3 楼的回答过于偏颇。并没有受限于 GIL 就让 thread 用处不大这种说法了,具体还是看应用场景,如果是计算密集的任务,为了充分利用多核,当然不会用 thread,而应该用 process。但是对于 IO 密集场景或者仅仅为了解决多路传输,那么 thread 是简单而有效的选择。而且#3 楼将两句话并发在一起,新手会以为 asyncio 不受限于 GIL 能够处理计算密集的任务。

个人认为,两者的主要差别是可以处理并发量的能力。可以根据你需要处理的异步 IO 的数量,比如:如果有 100 个 socket,那么给每个 socket 分别创建一个 thread 来处理,现在的计算机应该都能 hold 住。但是当 socket 数量更高,并发量更大的时候,那么就应该选择使用 asyncio 了。

楼上两位说得很好了。

一言不合就说 GIL 也是够了…

<iframe src="https://www.youtube.com/embed/P3AyI_u66Bw" class="embedded_video" allowfullscreen="" type="text/html" id="ytplayer" frameborder="0"></iframe>
&t=1141s

也要看看使用场景好吗…

方括号里说 io 复用,这个 asyncio 也是类似 epoll 可以得到网卡中断事件?如果只是在解释器内模拟,那这个东西做 io 大的并发估计也就一般吧

thread 是否是系统线程,进入调度队列?

比如你需要做三件事情,用电饭煲煮饭,用洗衣机洗衣服,用砂锅煲汤。

如果你用 thread,那么就需要三个人,第一个人把饭放进电饭煲,然后等着它煮好。第二个人把衣服放进洗衣机,等着它洗好,第三个人把食材放进砂锅,等着汤煲好。准备工作做完以后,这三个人都会啥事不干,傻等着。

如果你用 asyncio,那么你只需要一个人做这个事情。他先把米倒进电饭煲,打开开关开始煮饭,煮饭的过程电饭煲自己会做,不用这个人来管。所以中间的时间,他可以接下来去把衣服放进洗衣机,打开开关,洗衣机自动开始洗衣服,也不再需要他管了。于是他再去把食材放进砂锅,开始煲汤。开始煲汤以后,短时间里他还可以去看个书。如果汤漫出来了,它会听到声音,这个时候再去查看就好了。同样的,电饭煲煮饭煮好了会有提示音,洗衣机洗好了衣服也有提示音。它只需要听到提示音再去处理就好了。没有听到提示音,他就可以去做其他事情。

综上所述,
用 thread,做多少个事情就需要请多少个人,而且还有可能这些人会同时傻等,每一个人工资(占用的系统资源)可不便宜
用 asyncio,只需要一个人就可以把所有事情全部做完。

除了傻等 工作干完就不在占资源了吧 看来没必要学 thread 啊

GIL 日常背锅

thread threading 就是被 gil 搞得没人用的,俺说错了?

你说的没有错。

说的没有错,但是一 thread 并不是没人用,二在这个与 asyncio 比的场景,GIL 没锅

应该说不能说全错

GIL 日常锅

是系统级线程,但是只能映射到单个核。

虽然都只能利用单个核,但是 asyncio 没有上下文切换,优势在这里。

另外 asyncio 也有缺陷。就是你如果用了它,所有 io api 都要用异步(支持多路复用)的版本。要不然还是卡着。这就意味着可能很多库就不能用了。

看了下 thread 实现,是模拟线程,不是系统级线程,至少 2.7 的实现是模拟的

不要总是甩锅给 GIL,到底是怎么得出结论没人用 threading 的啊?

如果没有对应的 aio 库,把同步的操作丢给线程池来处理也是可以的。目前的异步文件操作( aiofile ),DNS 查询等操作,其实都是放在线程池里面的。真正能够高效处理的还是只有 socket。

不知道该说些什么。。。。。。

除了 opcode 外我大 Py 从来都不自己模拟东西好伐,至少在 Linux 下都是依赖 syscall 的。所以 thread 也是 syscall,只是从 VM 层面和开发者层面封装了一些,同时也加锁保护 VM 执行栈,所以才有了 GIL。

可以具体看一下模块源码,不是特别难懂的。

程序通常会遇到三种瓶颈:CPU,IO,内存
对策也有三种:多进程,多线程,异步 io/协程

多进程能利用多核 CPU,但内存开销大。

多线程在操作系统层面可以利用多核 CPU,但各种线程同步 /锁的问题,会导致 Python 解释器实现特别复杂,所以干脆加了个全局锁,只允许用一个核;线程在执行 io 操作阻塞时,系统会把线程挂起,把 CPU 分配给别的线程运行;内存开销比进程小。

异步 io 相当于在 Python 中实现
asyncio/协程相当于在 python 中实现一个内核 /调度系统,协程在进行 io 阻塞时,安排别的协程继续运行;内存开销更小。

如果你遇到 io 瓶颈,可以用多线程和协程,协程内存开销更小,能同时跑更多任务,web 服务能同时处理更多请求。

PS:asyncio 实现过于复杂,推荐 curio !

会 io 阻塞但没有异步 io 实现的库,可以用线程池封装成异步,也就是异步 io 和多线程可以一起用:P


我错了
看了 threadmodule.c,没有 grep 到 pthread_create,看文件又这么长,以为是模拟的线程
是系统线程,那为啥老说 python 线程性能差呢?

thread 模块被更高级的 threading 模块代替了。

由于 gil 的存在,threading 并不能提高 cpu 密集运算的性能。threading 以前用于提高 io 密集型的操作速度。

py3 新加的 asyncio 是用来做异步 io 的。

我很久没用 threading 了,并没有不适。

据说单核那个年代 Guido 在设计时偷懒了加了 GIL,没考虑过多核…

一直很费解,为什么这么多年了都不改进 cPython 的解释器

因为系统级线程和处理机(核)之间还有个映射,GIL 把这些线程都限制在一个核上了。

社区有人尝试过的,都没啥成效。别指望 CPython 改了。
其实放在 Web 开发领域也并无大碍,毕竟也胶水语言,可以放到本地线程栈去做计算密集,比如用 Cython 重写部分业务代码。

回到顶部