Python 中线程和协程的区别是什么 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
pureGirl
V2EX    程序员

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

  •  
  •   pureGirl 253 天前 5442 次点击
    这是一个创建于 253 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    33 条回复    2025-02-01 17:34:32 +08:00
    w568w
        3
    w568w  
       253 天前   1
    1. python 中有 GIL 所以不支持多个线程同时运行

    2. 协程又是什么和线程的区别是什么

    我怎么没看懂前后关系?

    正经回答:「协程」是一个过度滥用的概念(以及营销术语),你不说清楚具体语境,就有一百种不同的解释。

    掰扯这个词本身没什么意思,先说明白你想问的对象是什么。
    pursuer
        4
    pursuer  
       253 天前
    协程的切出点是可以确认的,所以考虑 data race 这类问题时会简单很多,协程的栈(当然我是指把闭包变量当做等价的栈)通常比线程小而且更加灵活
    julyclyde
        5
    julyclyde  
       253 天前
    线程是一个操作系统概念,是拥有 pid 的
    协程是一种语法结构
    zhangyb123
        6
    zhangyb123  
       253 天前
    python 在 GIL 的实现下,其多线程的并发可以理解为一个单核设备上不同的线程切换着运行,并不会像多核设备下多个线程并行执行。且 python 会每 5ms 就对占据 GIL 后正在运行的线程进行调度。即停止其运行,释放 GIL ,并重新调度其他线程抢占 GIL 后运行,这种行为是根据操作系统的线程机制来实现的。
    而协程,其实是应用层的一个机制,和操作系统无关,就是通过 yield 关键字,将程序逻辑切分,但是无论怎么编写,协程始终是在同一个线程里运行的。
    Ct5T66PVR1bW7b2z
        7
    Ct5T66PVR1bW7b2z  
       253 天前 via iPhone
    写爬虫或者 web ,协程真的快很多
    flyqie
        8
    flyqie  
       253 天前 via Android   1
    协程跟线程的区别是一个由编程语言负责调度,一个由操作系统进行调度

    本质上协程是线程的包装,协程底层还是走的线程的。
    wangritian
        9
    wangritian  
       253 天前
    cpu 线程:如果你有一块 8 核 cpu 并开启了超线程,那就有 16 个 cpu 线程可用
    操作系统线程:每个应用至少有一个进程,每个进程至少有一个线程,操作系统把应用创建的线程动态绑定到任意一个 cpu 线程上
    协程:在同一个线程内,应用(编程语言)负责切换上下文,操作系统和 cpu 看不到你在搞事情,最适合 IO 并发任务
    abc612008
        10
    abc612008  
       253 天前
    多线程:
    1. 同一时间可以有两个指令在被 CPU 执行。
    2. 线程是系统/硬件级的概念。
    3. 例子:咖啡店有两个(机器人)店员在同时做咖啡。(你也没法写软件多出一个店员)

    协程
    1. 同一时间只有一个指令在被执行,但是可以是完全不同地方的指令。
    2. 协程是应用级的概念。
    3. 一个店员在做两杯咖啡,在等加热的时候去“同时”做另一杯。(因此协程通常只有在 IO bound 的时候才比较有作用)

    当然也可以既有多线程也有协程。

    我觉得你的问题是,既然 GIL 导致没有办法同时真的执行两个 python 语句,那多线程不就和协程一样了。我的理解是 python 的多线程仍然是多个系统线程,而 GIL 只在 python 代码里会被 lock ,如果是在做 IO bound/syscalls 或者甚至 numpy,pandas 在做计算的时候都会 release GIL 。
    whileFalse
        11
    whileFalse  
       253 天前   1
    这玩意你用过 Windows3.1 就知道了。在 3.1 中,多任务的实现方式是程序主动放弃执行,这也导致了一旦有一个应用陷入死循环,整个系统就死了。

    协程的好处就是你的代码不会被莫名其妙的打断,不太容易出现多线程中的竞态条件,也不需要锁等东西。坏处是对于那些运行缓慢的代码块,无论是计算密集,还是 IO 密集但你忘了用异步写,都会让程序卡死。

    线程的好处是即使跑一个长过程也不会完全 block 住其他 IO 密集型的代码,但要处理多线程的那些问题。
    timerring
        12
    timerring  
       253 天前
    首先更正一点,只有在 CPython 解释器中才有 GIL 锁,其他解释器例如 JPython 中就没有 GIL 的问题,GIL 本质上是为了保证底层的 C 的存储安全。

    我最近写过一篇博客: https://blog.timerring.com/posts/cpu-can-only-see-the-threads

    看过能解决你关于 python 中 多线程 多进程 并发 并行 协程 GIL 上下文切换 锁 同步异步 的疑问

    核心中的核心就一句话 “CPU 只能看到线程” 如果你能真正理解这句话,你就能理解一切,不信可以读一读,也欢迎进一步斧正或者交流套讨论。

    另外我之前也写过在 go 中的实现方式,看过你就能理解 go 的设计到底比 python 好在哪里。具体可以找一找我的博客。
    xingheng
        13
    xingheng  
       253 天前
    @flyqie #8 不对啊喂
    协程不是线程的封装,底层也不是线程
    xingheng
        14
    xingheng  
       253 天前
    @julyclyde #5 线程有自己的 pid ?操作系统哪一章讲的,我复习一下?
    kaiveyoung
        15
    kaiveyoung  
       253 天前 via Android
    @xingheng 是有 pid 的,具体哪一章要看你用哪个版本的教材
    w568w
        16
    w568w  
       253 天前   1
    @w568w #3 隔了半天回来看,果然大部分回复都按自己的直觉先入为主了(没有说回复不对的意思):

    Python / Lua 的非抢占式协同调用(又称生成器),

    C++20 / Go 的无栈(堆)抢占式微线程,

    都能称为「协程」。

    还有说「协程一定是单线程的」,等谈到 Java/Kotlin 有 Scheduler 参与的协程时,又要懵逼了。

    另外,支持工作分发的 OpenMP 算不算协程?这也不好说。

    ----

    至于「线程」和「协程」的区别,品一下它们的目的就知道了:

    「线程」:操作系统调度 CPU 资源的最小单位。
    「协程」:多程序流协作运作的机制。


    「线程」是操作系统的约定,你给操作系统一个程序地址,系统就能为这个程序分配资源。
    「协程」是设计上的考虑,是开发者自己设计的、让进行不同逻辑的程序之间协作的机制。

    一言蔽之,这俩从概念上就八竿子打不着,根本不是一个 level 的东西。只不过现在很多协程的实现(例如无栈微线程)就是为了解决操作系统线程在协作方面存在的问题(太贵?要考虑并发?写起来麻烦?),所以往往需要涉及线程相关的知识。

    看英文也能看出:为什么线程叫 thread 而不是 routine ,协程叫 coroutine 而不是 cothread ?有没有可能它俩根本不是同一种概念?
    w568w
        17
    w568w  
       253 天前
    @w568w 手抖发出去了,补充一下:

    最后回答一下楼主的问题:仅在 Python 语境下,就是抢占式(操作系统轮转)和非抢占式(主动 yield )协作运算的区别。
    qbqbqbqb
        18
    qbqbqbqb  
       253 天前
    @xingheng “线程有 pid”是 linux 的特色,因为历史遗留问题 Linux 内核里不区分线程进程都统称 task 统一管理。

    在 Linux 里 pid 相当于别的系统里的线程 id ,tgid (线程组 id ,等于同一个进程中主线程的 pid )相当于别的系统里的 pid 。
    qbqbqbqb
        19
    qbqbqbqb  
       253 天前
    @kaiveyoung 不是普适的特性,只有 Linux 才是线程有 pid 。像 Windows 这种线程从属于进程的系统就是区分 pid 和 tid 的。
    dearmymy
        20
    dearmymy  
       253 天前
    说实话,能真正理解协程,代表对编程上了一个台阶。
    自己真正拿 c 实现下,好处超出想象
    mayli
        21
    mayli  
       253 天前
    @kaiveyoung 早期的系统进程和线程分的比较开,但是现在的基本上 thread 也会有 pid, 包括 linux.
    mayli
        22
    mayli  
       253 天前
    我觉得主要区别是
    线程是系统调度器抢占 cpu, 可以把进程 线程强制踢出 cpu
    协程大部分是阻塞时主动让出 cpu, coroutine 的 co 我觉得是 cooperative 。
    假如一个协程一直不阻塞,调度器也没法踢他。
    cj323
        23
    cj323  
       253 天前
    我的认知里 coroutine 比 thread 和 process 更轻量但是也能共享内存,不过一直没深入理解过。

    之前好奇过 python 的 green thread/gevent/asyncio 这些概念有没有区别。以及跟 Erlang 的 process ,Go 的 goroutine ,Node 的 async ,和 Rust 的 tokio 之间有没区别。
    mayli
        24
    mayli  
       252 天前
    @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 对于没有精神洁癖的人来说,性能也过得去,用起来也不难受。
    dearmymy
        26
    dearmymy  
       252 天前
    说下我得理解。
    首先进程是有自己一套全部资源。
    线程是最小得执行单位,但是绝对大部分资源变量要跟其他线程共享,一个程序运行,至少也有一个主线程进行运行。多跟线程,就是多套执行单元。你起了 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
        27
    julyclyde  
       252 天前
    @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
        28
    WorseIsBetter  
       252 天前
    @julyclyde #27

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

    Linux 2.4 引入了 thread group 的概念,并支持了 CLONE_THREAD 。基于新内核特性的 NPTL (沿用至今的 glibc pthreads 实现)就没有这样的限制。
    lisongeee
        29
    lisongeee  
       252 天前
    上面的评论出现了 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
        30
    mayli  
       252 天前
    @lisongeee 有必要补充一下,现在这个 nogil 就是个鸡肋
    - 慢,开 nogil 有些优化就用不上,导致解释器变慢
    - 没库,一堆 native extension 库用不上了
    - py 大头 web server 有一堆 prefork 的比如 gunicorn ,没有 nogil 兼容
    - asyncio 的库里也没有 nogil 的
    感觉 nogil 之于 3.13 就类似 asyncio 之于 python3.4
    yuuluu
        31
    yuuluu  
       251 天前
    自己实现一个协程就知道怎么回事了.
    这里推荐 David Beazley 在 2015 pycon 上的 talk, 50 分钟边讲边写代码.

    julyclyde
        32
    julyclyde  
       250 天前
    @WorseIsBetter 内核里 pid 和 tid 是在同一个地方编号的吗?
    WorseIsBetter
        33
    WorseIsBetter  
       250 天前   1
    @julyclyde #32

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

    这里的 pid 就是用户态的 tid ,tgid 则是用户态的 pid
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1152 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 86ms UTC 23:28 PVG 07:28 LAX 16:28 JFK 19:28
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86