<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: jianfeng huang</title>
    <description>The latest articles on DEV Community by jianfeng huang (@jianfeng_huang_5dc66197a0).</description>
    <link>https://dev.to/jianfeng_huang_5dc66197a0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2181348%2F280d49c3-ed1b-4092-a7b3-2751090e533f.jpg</url>
      <title>DEV Community: jianfeng huang</title>
      <link>https://dev.to/jianfeng_huang_5dc66197a0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jianfeng_huang_5dc66197a0"/>
    <language>en</language>
    <item>
      <title>用大白话讲清楚：协程到底是个什么东西？</title>
      <dc:creator>jianfeng huang</dc:creator>
      <pubDate>Sat, 03 Oct 2026 00:41:58 +0000</pubDate>
      <link>https://dev.to/jianfeng_huang_5dc66197a0/yong-da-bai-hua-jiang-qing-chu-xie-cheng-dao-di-shi-ge-shi-yao-dong-xi--5bgg</link>
      <guid>https://dev.to/jianfeng_huang_5dc66197a0/yong-da-bai-hua-jiang-qing-chu-xie-cheng-dao-di-shi-ge-shi-yao-dong-xi--5bgg</guid>
      <description>&lt;p&gt;先把结论摆出来，不绕弯子：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;普通函数是「一次性」的：一旦调用，就必须一路跑到 return 或者抛异常。&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;协程是「可以暂停、可以恢复」的函数：它跑到一半能把控制权交出去，等条件满足了再回来接着跑。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;协程不是什么新奇的线程，也不是什么黑魔法。它本质上就是&lt;strong&gt;一种能把执行过程切成小段、并且每段的边界由代码自己说了算的调用方式&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;下面我把这个句子拆开讲。&lt;/p&gt;

&lt;h2&gt;
  
  
  一、先看你熟悉的那个世界
&lt;/h2&gt;

&lt;p&gt;平时写的函数是这样的：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;check_stock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# 必须执行完
&lt;/span&gt;    &lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# 必须执行完
&lt;/span&gt;    &lt;span class="nf"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# 必须执行完
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;调用 &lt;code&gt;pay(order)&lt;/code&gt;，Python 就得从第一行走到最后一行。中间 &lt;code&gt;send_email&lt;/code&gt; 要等 3 秒，这 3 秒里整个线程就卡在那儿，什么也干不了。&lt;/p&gt;

&lt;p&gt;这是「一次性」的含义：&lt;strong&gt;函数的执行过程属于调用者，函数自己交不出控制权。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;问题就出在这里。后端服务大量时间不是在算，而是在等 —— 等数据库、等第三方接口、等磁盘。线程在等的过程中，明明什么都没干，却占着一个线程资源不放。&lt;/p&gt;

&lt;h2&gt;
  
  
  二、协程：让函数做到一半先撤
&lt;/h2&gt;

&lt;p&gt;想象你在厨房炒菜，炒到一半发现酱油没了。你有两种做法：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;线程的做法&lt;/strong&gt;：关火，站在灶台前死等外卖送酱油，这期间灶台被你占着，别人没法用。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;协程的做法&lt;/strong&gt;：把锅放一边，跟厨房里的其他人说一句「我缺酱油，谁先用灶」，然后去干别的；酱油到了再回来接着炒。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;第二种做法里，那个「炒菜的过程」就是协程：&lt;strong&gt;它是一个可以被打断、被挂起、之后还能从断点继续的过程。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Python 里最原始的协程形态是生成器，&lt;code&gt;yield&lt;/code&gt; 就是那个「暂停点」：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cook&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1. 热锅下油&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;酱油没了，我先去买&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;      &lt;span class="c1"&gt;# 在这里「暂停」，控制权交回调用方
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2. 酱油回来了，接着炒&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;炒好了，装盘&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3. 上桌&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;调用 cook() 拿到的不是菜，而是一个对象:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;第一次 next -&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;……等待过程中，我可以去顺手把米饭煮上&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;第二次 next -&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;真实输出：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;调用 cook() 拿到的不是菜，而是一个对象: &amp;lt;generator object cook at 0x100e06ec0&amp;gt;
1. 热锅下油
第一次 next -&amp;gt; 酱油没了，我先去买
……等待过程中，我可以去顺手把米饭煮上
2. 酱油回来了，接着炒
第二次 next -&amp;gt; 炒好了，装盘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;注意两点，这是理解协程的关键：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cook()&lt;/code&gt; 调用完，函数体一行都没执行。&lt;/strong&gt; 它只是造出了一个对象，真正跑起来要靠 &lt;code&gt;next()&lt;/code&gt; 喂它。所以协程天生是「懒」的、可被外部驱动的。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;中间那句「顺手把米饭煮上」是插在两段之间的。&lt;/strong&gt; 也就是说，协程暂停的这段时间，调用方可以自由地去干别的事。这就是「让出控制权」的全部含义。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  三、async/await 只是把这套东西变得好用了
&lt;/h2&gt;

&lt;p&gt;生成器 + &lt;code&gt;yield&lt;/code&gt; 太反直觉，写起来像在拼装零件。所以 Python 3.5 给了 &lt;code&gt;async/await&lt;/code&gt; 语法糖：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] 开始请求&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# 关键：等待时把控制权交还给事件循环
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] 返回了&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;await asyncio.sleep(1)&lt;/code&gt; 和 &lt;code&gt;yield&lt;/code&gt; 在做同一件事：&lt;strong&gt;「我这儿要等 1 秒，事件循环你先去忙别的，1 秒后再回来叫我。」&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;而那个「忙着调度别人的家伙」就是事件循环（event loop）。你可以把它理解成厨房里的厨师长：谁在等东西，就把谁的锅端下来，把灶让给下一个能立刻干活的人。&lt;/p&gt;

&lt;p&gt;跑一下两个协程：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;总耗时 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[A] 开始请求
[B] 开始请求
[A] 返回了
[B] 返回了
总耗时 1.00s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;两个各等 1 秒的任务，总共只花了 1 秒。因为 A 在等的时候，B 已经把请求发出去了 —— 谁都没闲着。&lt;/p&gt;

&lt;h2&gt;
  
  
  四、为什么不能直接写 &lt;code&gt;time.sleep&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;这是最容易踩的坑，也是最能说明协程本质的一段。把上面的 &lt;code&gt;await asyncio.sleep(1)&lt;/code&gt; 换成阻塞的 &lt;code&gt;time.sleep(1)&lt;/code&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] 开始请求&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;               &lt;span class="c1"&gt;# 阻塞式睡眠：整个线程都停在这
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] 返回了&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;结果立刻退化：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[A] 开始请求
[A] 返回了
[B] 开始请求
[B] 返回了
总耗时 2.01s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;变回串行了。原因很直白：&lt;strong&gt;&lt;code&gt;time.sleep&lt;/code&gt; 不会把控制权交给事件循环&lt;/strong&gt;，它让整个线程真真切切地睡着了。线程睡着了，事件循环也被冻住，谁都没法调度。&lt;/p&gt;

&lt;p&gt;所以记住这句话，比背十遍定义管用：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;协程的「并发」不是凭空产生的，它靠的是「每个任务在等待时主动让出」。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;一旦有谁不让出（&lt;code&gt;time.sleep&lt;/code&gt;、同步的 &lt;code&gt;requests&lt;/code&gt;、CPU 密集的循环），整条流水线就全堵死了。这是协程和线程最本质的分歧，下一节展开。&lt;/p&gt;

&lt;h2&gt;
  
  
  五、和线程到底差在哪：谁决定「换人」
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;线程是抢占式的&lt;/strong&gt;：切换的时机由操作系统内核说了算，程序自己管不着。哪怕你写了个死循环，内核照样会把 CPU 抢走分给别人：
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  t=0.32s 另一个线程照样拿到了 CPU（内核抢走的）
  t=1.50s 忙线程自己跑完了
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;协程是协作式的&lt;/strong&gt;：切换只发生在代码显式 &lt;code&gt;await&lt;/code&gt;（或 &lt;code&gt;yield&lt;/code&gt;）的地方。没有 &lt;code&gt;await&lt;/code&gt;，就没有切换。好处是切换点全都在你眼皮底下，天然没有竞态、不用到处加锁；代价是你必须自己保证「该让出的时候让出了」。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一句话对比：&lt;strong&gt;线程的调度权在内核，协程的调度权在你自己手里。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;同样重要的是：协程跑在单个线程上，所以&lt;strong&gt;它不提供任何并行能力&lt;/strong&gt;。它的价值是「一个线程处理更多 IO 等待」，而不是「让计算更快」。CPU 密集的活儿交给协程，只会是负优化 —— 因为它把多核白白浪费了。&lt;/p&gt;

&lt;h2&gt;
  
  
  六、类比在哪里会失效
&lt;/h2&gt;

&lt;p&gt;用「炒菜换灶」打比方很好懂，但要小心这两点，别被比喻带歪：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;协程不是「半个线程」。&lt;/strong&gt; 它没有自己的栈切换开销那么大，也不是由操作系统管理的对象，它就是一堆被事件循环驱动的 Python 对象（&lt;code&gt;coroutine&lt;/code&gt; / &lt;code&gt;Task&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;不是「真的同时在做」。&lt;/strong&gt; 在任意一个瞬间，一个事件循环里只有一段协程代码在跑。看起来的「同时」是多个任务在等待点之间快速交替的结果。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;顺手验证一下「协程是懒的」这件事：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;coro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;# 注意：没有任何输出
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello() 只是造出了一个协程对象:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hello() 只是造出了一个协程对象: &amp;lt;coroutine object hello at 0x103e0af80&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;不 &lt;code&gt;await&lt;/code&gt; 它，它就一直躺着不动 —— 这也是「coroutine was never awaited」这条警告的来源。&lt;/p&gt;

&lt;h2&gt;
  
  
  七、那什么时候别用协程
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU 密集&lt;/strong&gt;：图像处理、加密、大量计算 —— 用多进程，协程帮不上忙，还会拖慢。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;生态是同步的&lt;/strong&gt;：依赖一堆只提供同步 SDK 的库，为了用协程到处 &lt;code&gt;run_in_executor&lt;/code&gt;，复杂度比收益高。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;团队不熟&lt;/strong&gt;：协程的坑（阻塞调用混进来、忘记 &lt;code&gt;await&lt;/code&gt;、超时没设）在线上会以「整个服务卡死」的形式出现，排查成本不低。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  八、一张表收尾
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;进程&lt;/th&gt;
&lt;th&gt;线程&lt;/th&gt;
&lt;th&gt;协程&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;调度者&lt;/td&gt;
&lt;td&gt;操作系统内核&lt;/td&gt;
&lt;td&gt;操作系统内核&lt;/td&gt;
&lt;td&gt;你自己的代码 / 事件循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;切换时机&lt;/td&gt;
&lt;td&gt;抢占式，随时&lt;/td&gt;
&lt;td&gt;抢占式，随时&lt;/td&gt;
&lt;td&gt;协作式，只在 &lt;code&gt;await&lt;/code&gt; 处&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;切换成本&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;真并行&lt;/td&gt;
&lt;td&gt;是（多核）&lt;/td&gt;
&lt;td&gt;是（多核，受 GIL 影响）&lt;/td&gt;
&lt;td&gt;否，单线程交替&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适合&lt;/td&gt;
&lt;td&gt;CPU 密集&lt;/td&gt;
&lt;td&gt;混合负载&lt;/td&gt;
&lt;td&gt;IO 密集、高并发等待&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  回到最初那句话
&lt;/h2&gt;

&lt;p&gt;协程就是&lt;strong&gt;一个能暂停、能恢复的函数&lt;/strong&gt;，加上一个负责「在谁等待时把执行权交给别人」的调度器（事件循环）。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;async/await&lt;/code&gt; 只是语法糖，生成器的 &lt;code&gt;yield&lt;/code&gt; 才是它最朴素的形态；而「并发」这件事，来自每个任务在等待时&lt;strong&gt;主动让出&lt;/strong&gt;，不是来自多出来的 CPU。&lt;/p&gt;

&lt;p&gt;搞清这两点，面对 &lt;code&gt;coroutine was never awaited&lt;/code&gt;、&lt;code&gt;event loop is closed&lt;/code&gt;、以及「为什么我加了 async 反而更慢了」这三类报错，你基本都能自己推出原因了。&lt;/p&gt;

</description>
      <category>python</category>
      <category>async</category>
      <category>coroutine</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
