Python 中线程和协程的区别是什么

225 天前
 pureGirl

python 中有 GIL 所以不支持多个线程同时运行,那协程又是什么和线程的区别是什么

5306 次点击
所在节点    程序员
33 条回复
mayli
225 天前
@kaiveyoung 早期的系统进程和线程分的比较开,但是现在的基本上 thread 也会有 pid, 包括 linux.
mayli
225 天前
我觉得主要区别是
线程是系统调度器抢占 cpu, 可以把进程 线程强制踢出 cpu
协程大部分是阻塞时主动让出 cpu, coroutine 的 co 我觉得是 cooperative 。
假如一个协程一直不阻塞,调度器也没法踢他。
cj323
225 天前
我的认知里 coroutine 比 thread 和 process 更轻量但是也能共享内存,不过一直没深入理解过。

之前好奇过 python 的 green thread/gevent/asyncio 这些概念有没有区别。以及跟 Erlang 的 process ,Go 的 goroutine ,Node 的 async ,和 Rust 的 tokio 之间有没区别。
mayli
224 天前
@cj323 简单说,底层是一样的,或者只有一层上面出来两套
一套是 blocking io ,另一套是 non-blocking io
大部分的 coroutine 都是解决网络 io ( asyncio 默认都不处理本地文件 io ),场景是大部分时间 cpu 都在等网络 io , 比如 webapp 等 db 之类。
python 的话,除了 GIL 部分,gevent 使用的是隐式的方法,相当于所有进到底层 blocking io 的地方,都包( patch )了一遍,强行改成了异步的办法,库用的是 libuv/libev
asyncio 用的是显式的写法,你所有碰 io 的地方,都得 asyncio ,然后 asyncio 库再去实现一个 event loop ,然后如果你恰巧用的是 uvloop, 那就跟 gevent+libuv 底层一样了。

对于 go ,由于 goroutine 的 async 是语言级,不是一个库,他实际上可以理解为 gevent 的风格,直接底层把 io 部分包好了。

对于 nodejs ,单线程的部分跟 py 很像,甚至 libuv 本身就是 nodejs 出来的,不过语法上也是要显式的使用 async.

tokio 的话,对应的位置应该是 uvloop 。rust 本身 std 有个 async ,tokio 相当于从 0 造了个轮子,包含了 uvloop+libuv 。

语法上要是根据有无显式 async 的话,gevent+go 是一类,其他的都需要显式的写 async await. 底层上除了 tokio/go ,都可以偷懒直接套现有的 event 库,比如 libuv.
综合来看,go 的 async 实现最优雅(原生内置),gevent 对于没有精神洁癖的人来说,性能也过得去,用起来也不难受。
PTLin
224 天前
dearmymy
224 天前
说下我得理解。
首先进程是有自己一套全部资源。
线程是最小得执行单位,但是绝对大部分资源变量要跟其他线程共享,一个程序运行,至少也有一个主线程进行运行。多跟线程,就是多套执行单元。你起了 10 个线程后,是操作系统在控制执行。 可以简单记住,是操作系统在执行,可以多个线程同时执行
协程可以理解是单个线程下的调度方式。所有的执行顺序是程序主动控制的。 之所以单线程可以做到跟多线程一样,是因为针对很多 io 操作时候,去执行其他代码。所以看起来像是多线程。
假设一个函数是
void request_file()
{
request_fileA()
request_fileB()
} 里面分别去请求 A B 两个文件。网络请求是 IO 行为,事实上当 ring3 层请求 A 文件后,处理 io 操作在 ring0 层,ring0 层处理完成后会通知 ring3 ,然后在执行请求 B ,在这个过程中其实是浪费时间,因为请求 A 后等待时间没必要等待。这时候可以直接请求 B ,等 A 完成 io 后在回来继续执行 A 的函数。 这个在单线程里来回乱跳执行,就要在执行 A 需要等待 io 时候,把当前寄存器 栈信息全部保存在一个 context 里,方便恢复环境,强行更改 pc 寄存器去跳转执行 B 函数,在过程中 ring0 通知 io 执行完成,在恢复 A 的 context 把 A 的继续执行。 整个过程就是利用 io 空闲时间,几个函数来回跳,实现在一个线程里看起来像多线程。
这就说明执行过程中 io 操作越多,协程就优势更大。像爬虫或者操作大文件协程就很适合。但是如果都是 cpu 根本抽不出 io 时间间隔,跟执行单线程同步函数一样了。这时候就多线程好。

之前 pc 客户端操作。比如点击一个 button ,去请求一个大数据,并展示在 ui 上。没协程时候,为了不卡界面。只能开线程,或者异步函数,但是异步函数就要有回调。 简单一个功能代码乱的一批。 但是用协程就超级简单。逻辑清晰。
julyclyde
224 天前
@xingheng
https://man7.org/linux/man-pages/man7/pthreads.7.html
• Threads do not share process IDs. (In effect, LinuxThreads
threads are implemented as processes which share more
information than usual, but which do not share a common
process ID.) LinuxThreads threads (including the manager
thread) are visible as separate processes using ps(1).
WorseIsBetter
224 天前
@julyclyde #27

不过线程不共享 PID 已经是遥远的过去了( LinuxThreads 早在 glibc 2.4 ,也就是 2006 年的时候就不再受支持)。

Linux 2.4 引入了 thread group 的概念,并支持了 CLONE_THREAD 。基于新内核特性的 NPTL (沿用至今的 glibc pthreads 实现)就没有这样的限制。
lisongeee
224 天前
上面的评论出现了 15 个 python GIL 关键词

有必要补充一下,python(cpython) 在 3.13 就已支持无 GIL 运行(自由线程模式的实验性支持)

https://docs.python.org/zh-cn/3.13/whatsnew/3.13.html#whatsnew313-free-threaded-cpython
mayli
224 天前
@lisongeee 有必要补充一下,现在这个 nogil 就是个鸡肋
- 慢,开 nogil 有些优化就用不上,导致解释器变慢
- 没库,一堆 native extension 库用不上了
- py 大头 web server 有一堆 prefork 的比如 gunicorn ,没有 nogil 兼容
- asyncio 的库里也没有 nogil 的
感觉 nogil 之于 3.13 就类似 asyncio 之于 python3.4
yuuluu
223 天前
自己实现一个协程就知道怎么回事了.
这里推荐 David Beazley 在 2015 pycon 上的 talk, 50 分钟边讲边写代码.

<amp-youtube data-videoid="MCs5OvhV9S4" layout="responsive" width="480" height="270"></amp-youtube>
julyclyde
222 天前
@WorseIsBetter 内核里 pid 和 tid 是在同一个地方编号的吗?
WorseIsBetter
222 天前
@julyclyde #32

是的。见: https://github.com/torvalds/linux/blob/v6.13/include/linux/sched.h#L1025

这里的 pid 就是用户态的 tid ,tgid 则是用户态的 pid

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

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

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

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

© 2021 V2EX