<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Parallel on Tarragon</title><link>https://tarrragon.github.io/blog/tags/parallel/</link><description>Recent content in Parallel on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 21 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/parallel/index.xml" rel="self" type="application/rss+xml"/><item><title>8.1 並行處理實戰</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/</guid><description>&lt;p>本章將入門系列學到的 &lt;code>concurrent.futures&lt;/code> 知識，應用於 &lt;code>.claude/lib&lt;/code> 中的真實程式碼，展示如何識別並行化機會、實作並行版本，以及測量效能差異。&lt;/p>
&lt;h2 id="學習目標">學習目標&lt;/h2>
&lt;p>完成本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>識別並行化機會&lt;/strong>：判斷一段程式碼是否適合並行化&lt;/li>
&lt;li>&lt;strong>應用 ThreadPoolExecutor&lt;/strong>：將循序處理改寫為並行版本&lt;/li>
&lt;li>&lt;strong>實作進度報告&lt;/strong>：使用 &lt;code>as_completed()&lt;/code> 追蹤任務完成狀態&lt;/li>
&lt;li>&lt;strong>處理並行錯誤&lt;/strong>：避免單一任務失敗影響整體執行&lt;/li>
&lt;li>&lt;strong>測量效能差異&lt;/strong>：用數據證明優化效果&lt;/li>
&lt;/ol>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;p>本章假設你已經讀過入門系列的並行處理章節：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理&lt;/a> - &lt;code>concurrent.futures&lt;/code> 基本用法&lt;/li>
&lt;/ul>
&lt;p>如果你對 &lt;code>ThreadPoolExecutor&lt;/code>、&lt;code>executor.map()&lt;/code>、&lt;code>as_completed()&lt;/code> 還不熟悉，建議先閱讀該章節。&lt;/p>
&lt;hr>
&lt;h2 id="識別並行化機會">識別並行化機會&lt;/h2>
&lt;h3 id="io-密集-vs-cpu-密集快速判斷法">I/O 密集 vs CPU 密集：快速判斷法&lt;/h3>
&lt;p>在入門系列中，我們學到 I/O 密集任務適合 &lt;code>ThreadPoolExecutor&lt;/code>。但實際程式碼中，如何快速判斷？&lt;/p>
&lt;h4 id="問自己這個問題程式在等什麼">問自己這個問題：程式在等什麼？&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 模式 1：等待外部資源（I/O 密集）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 等網路&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 等磁碟&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cursor&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 等資料庫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 模式 2：純計算（CPU 密集）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10_000_000&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># 沒有等待，純運算&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="獨立任務的識別">獨立任務的識別&lt;/h3>
&lt;p>並行化的前提是&lt;strong>任務之間互相獨立&lt;/strong>。識別方法：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">獨立性檢查清單：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">[ ] 任務之間沒有共享可變狀態？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">[ ] 任務執行順序不影響結果？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">[ ] 任務 B 不依賴任務 A 的輸出？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">如果三個都勾選，就適合並行化。&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="真實案例markdown_link_checkerpy">真實案例：markdown_link_checker.py&lt;/h3>
&lt;p>讓我們看一個真實的例子。以下是 &lt;code>markdown_link_checker.py&lt;/code> 中的 &lt;code>check_directory()&lt;/code> 方法：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 原始版本：循序處理&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_directory&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查目錄下所有 Markdown 檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集所有 .md 檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rglob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 循序檢查每個檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">md_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">md_files&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">md_file&lt;/span>&lt;span class="p">)))&lt;/span> &lt;span class="c1"># 一個接一個&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段程式碼適合並行化嗎？讓我們用檢查清單分析：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>問題&lt;/th>
 &lt;th>分析&lt;/th>
 &lt;th>結論&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>任務之間有共享狀態？&lt;/td>
 &lt;td>&lt;code>results&lt;/code> 只在主執行緒操作&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>執行順序影響結果？&lt;/td>
 &lt;td>檢查檔案 A 不影響檔案 B&lt;/td>
 &lt;td>不影響&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>任務互相依賴？&lt;/td>
 &lt;td>每個檔案獨立檢查&lt;/td>
 &lt;td>不依賴&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>是 I/O 密集？&lt;/td>
 &lt;td>&lt;code>check_file()&lt;/code> 讀取檔案內容&lt;/td>
 &lt;td>是&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>結論：&lt;strong>非常適合並行化&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="threadpoolexecutor-實戰">ThreadPoolExecutor 實戰&lt;/h2>
&lt;h3 id="步驟-1確認可並行化的函式">步驟 1：確認可並行化的函式&lt;/h3>
&lt;p>首先，確認被並行化的函式是「純函式」或至少沒有副作用：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查單個 Markdown 檔案的連結
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> 特性：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 輸入：檔案路徑
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 輸出：檢查結果
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 沒有修改外部狀態
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 沒有依賴執行順序
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 實作省略&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>check_file()&lt;/code> 滿足條件：&lt;/p>
&lt;ul>
&lt;li>只讀取檔案，不修改&lt;/li>
&lt;li>返回獨立的結果物件&lt;/li>
&lt;li>不依賴其他檔案的結果&lt;/li>
&lt;/ul>
&lt;h3 id="步驟-2改寫為並行版本">步驟 2：改寫為並行版本&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">concurrent.futures&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ThreadPoolExecutor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">as_completed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_directory_parallel&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">max_workers&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;並行檢查目錄下所有 Markdown 檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集所有 .md 檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rglob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 並行檢查&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">ThreadPoolExecutor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_workers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">max_workers&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">executor&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 提交所有任務&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">futures&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">executor&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">submit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_file&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">md_file&lt;/span>&lt;span class="p">)):&lt;/span> &lt;span class="n">md_file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">md_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">md_files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">future&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">as_completed&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">futures&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">future&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步驟-3選擇-max_workers">步驟 3：選擇 max_workers&lt;/h3>
&lt;p>&lt;code>max_workers&lt;/code> 的選擇影響效能：&lt;/p></description><content:encoded><![CDATA[<p>本章將入門系列學到的 <code>concurrent.futures</code> 知識，應用於 <code>.claude/lib</code> 中的真實程式碼，展示如何識別並行化機會、實作並行版本，以及測量效能差異。</p>
<h2 id="學習目標">學習目標</h2>
<p>完成本章後，你將能夠：</p>
<ol>
<li><strong>識別並行化機會</strong>：判斷一段程式碼是否適合並行化</li>
<li><strong>應用 ThreadPoolExecutor</strong>：將循序處理改寫為並行版本</li>
<li><strong>實作進度報告</strong>：使用 <code>as_completed()</code> 追蹤任務完成狀態</li>
<li><strong>處理並行錯誤</strong>：避免單一任務失敗影響整體執行</li>
<li><strong>測量效能差異</strong>：用數據證明優化效果</li>
</ol>
<h2 id="先備知識">先備知識</h2>
<p>本章假設你已經讀過入門系列的並行處理章節：</p>
<ul>
<li><a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a> - <code>concurrent.futures</code> 基本用法</li>
</ul>
<p>如果你對 <code>ThreadPoolExecutor</code>、<code>executor.map()</code>、<code>as_completed()</code> 還不熟悉，建議先閱讀該章節。</p>
<hr>
<h2 id="識別並行化機會">識別並行化機會</h2>
<h3 id="io-密集-vs-cpu-密集快速判斷法">I/O 密集 vs CPU 密集：快速判斷法</h3>
<p>在入門系列中，我們學到 I/O 密集任務適合 <code>ThreadPoolExecutor</code>。但實際程式碼中，如何快速判斷？</p>
<h4 id="問自己這個問題程式在等什麼">問自己這個問題：程式在等什麼？</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 模式 1：等待外部資源（I/O 密集）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>      <span class="c1"># 等網路</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">content</span> <span class="o">=</span> <span class="n">file</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>             <span class="c1"># 等磁碟</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>    <span class="c1"># 等資料庫</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 模式 2：純計算（CPU 密集）</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10_000_000</span><span class="p">))</span>  <span class="c1"># 沒有等待，純運算</span></span></span></code></pre></div><h3 id="獨立任務的識別">獨立任務的識別</h3>
<p>並行化的前提是<strong>任務之間互相獨立</strong>。識別方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">獨立性檢查清單：
</span></span><span class="line"><span class="ln">2</span><span class="cl">[ ] 任務之間沒有共享可變狀態？
</span></span><span class="line"><span class="ln">3</span><span class="cl">[ ] 任務執行順序不影響結果？
</span></span><span class="line"><span class="ln">4</span><span class="cl">[ ] 任務 B 不依賴任務 A 的輸出？
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">如果三個都勾選，就適合並行化。</span></span></code></pre></div><h3 id="真實案例markdown_link_checkerpy">真實案例：markdown_link_checker.py</h3>
<p>讓我們看一個真實的例子。以下是 <code>markdown_link_checker.py</code> 中的 <code>check_directory()</code> 方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 原始版本：循序處理</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查目錄下所有 Markdown 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 循序檢查每個檔案</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)))</span>  <span class="c1"># 一個接一個</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><p>這段程式碼適合並行化嗎？讓我們用檢查清單分析：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>分析</th>
          <th>結論</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>任務之間有共享狀態？</td>
          <td><code>results</code> 只在主執行緒操作</td>
          <td>沒有</td>
      </tr>
      <tr>
          <td>執行順序影響結果？</td>
          <td>檢查檔案 A 不影響檔案 B</td>
          <td>不影響</td>
      </tr>
      <tr>
          <td>任務互相依賴？</td>
          <td>每個檔案獨立檢查</td>
          <td>不依賴</td>
      </tr>
      <tr>
          <td>是 I/O 密集？</td>
          <td><code>check_file()</code> 讀取檔案內容</td>
          <td>是</td>
      </tr>
  </tbody>
</table>
<p>結論：<strong>非常適合並行化</strong>。</p>
<hr>
<h2 id="threadpoolexecutor-實戰">ThreadPoolExecutor 實戰</h2>
<h3 id="步驟-1確認可並行化的函式">步驟 1：確認可並行化的函式</h3>
<p>首先，確認被並行化的函式是「純函式」或至少沒有副作用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    檢查單個 Markdown 檔案的連結
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    特性：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    - 輸入：檔案路徑
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    - 輸出：檢查結果
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 沒有修改外部狀態
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 沒有依賴執行順序
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># ... 實作省略</span></span></span></code></pre></div><p><code>check_file()</code> 滿足條件：</p>
<ul>
<li>只讀取檔案，不修改</li>
<li>返回獨立的結果物件</li>
<li>不依賴其他檔案的結果</li>
</ul>
<h3 id="步驟-2改寫為並行版本">步驟 2：改寫為並行版本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_parallel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">8</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;並行檢查目錄下所有 Markdown 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 並行檢查</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)):</span> <span class="n">md_file</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># 收集結果</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="步驟-3選擇-max_workers">步驟 3：選擇 max_workers</h3>
<p><code>max_workers</code> 的選擇影響效能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># I/O 密集任務：可以設定較高</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 經驗法則：CPU 核心數的 2-4 倍</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">max_workers</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 或根據檔案數量動態調整</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">get_optimal_workers</span><span class="p">(</span><span class="n">file_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;根據檔案數量決定 worker 數量&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">cpu_count</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 檔案少時不需要太多 worker</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">if</span> <span class="n">file_count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="n">file_count</span><span class="p">,</span> <span class="n">cpu_count</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 檔案多時，I/O 密集可以多開一些</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="n">cpu_count</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span></span></span></code></pre></div><h4 id="為什麼-io-密集可以超過-cpu-核心數">為什麼 I/O 密集可以超過 CPU 核心數？</h4>
<p>因為執行緒在等待 I/O 時會釋放 GIL，其他執行緒可以繼續執行。如果有 8 個核心，但每個任務有 80% 時間在等待 I/O，那開 16-32 個 worker 可以更充分利用 CPU。</p>
<hr>
<h2 id="進度報告與錯誤處理">進度報告與錯誤處理</h2>
<h3 id="使用-as_completed-報告進度">使用 as_completed() 報告進度</h3>
<p><code>as_completed()</code> 返回一個迭代器，任務完成時立即 yield，適合顯示進度：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">progress_callback</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="kc">None</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    並行檢查目錄，支援進度回報
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        progress_callback: 回呼函式 (completed, total, current_file)
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">md_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="c1"># 建立 future -&gt; 檔案 的映射</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">completed</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">completed</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="n">current_file</span> <span class="o">=</span> <span class="n">futures</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="c1"># 回報進度</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">if</span> <span class="n">progress_callback</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">                <span class="n">progress_callback</span><span class="p">(</span><span class="n">completed</span><span class="p">,</span> <span class="n">total</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">current_file</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">def</span> <span class="nf">print_progress</span><span class="p">(</span><span class="n">completed</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">current_file</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">percent</span> <span class="o">=</span> <span class="p">(</span><span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">percent</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">% - </span><span class="si">{</span><span class="n">current_file</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="n">results</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="s2">&#34;docs/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="n">progress_callback</span><span class="o">=</span><span class="n">print_progress</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="nb">print</span><span class="p">()</span>  <span class="c1"># 換行</span></span></span></code></pre></div><h3 id="錯誤處理不讓單一失敗拖垮全部">錯誤處理：不讓單一失敗拖垮全部</h3>
<p>並行處理時，單一任務的異常不應該中斷其他任務：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_robust</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">8</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    並行檢查，分開返回成功結果和錯誤
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        (成功的結果列表, 錯誤資訊列表)
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">errors</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">file_path</span> <span class="o">=</span> <span class="n">futures</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="c1"># 記錄錯誤，繼續處理其他檔案</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                    <span class="s2">&#34;file&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                    <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                    <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="nb">type</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">return</span> <span class="n">results</span><span class="p">,</span> <span class="n">errors</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="n">results</span><span class="p">,</span> <span class="n">errors</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_robust</span><span class="p">(</span><span class="s2">&#34;docs/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;成功檢查 </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2"> 個檔案&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="k">if</span> <span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;有 </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> 個錯誤：&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">err</span><span class="p">[</span><span class="s1">&#39;file&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">err</span><span class="p">[</span><span class="s1">&#39;error&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><hr>
<h2 id="效能測量">效能測量</h2>
<h3 id="使用-timeit-比較效能">使用 timeit 比較效能</h3>
<p>理論上並行會更快，但「更快多少」需要測量：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 準備測試資料</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">test_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;docs/&#34;</span><span class="p">)</span>  <span class="c1"># 假設有 50+ 個 .md 檔案</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 測量循序版本</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">test_sequential</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">test_dir</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">sequential_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">test_sequential</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span> <span class="o">/</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 測量並行版本</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">test_parallel</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_parallel</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">test_dir</span><span class="p">),</span> <span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">parallel_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">test_parallel</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span> <span class="o">/</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># 計算加速比</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">speedup</span> <span class="o">=</span> <span class="n">sequential_time</span> <span class="o">/</span> <span class="n">parallel_time</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本：</span><span class="si">{</span><span class="n">sequential_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行版本：</span><span class="si">{</span><span class="n">parallel_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比：</span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="真實測試結果參考">真實測試結果參考</h3>
<p>以下是在不同檔案數量下的測試結果（供參考）：</p>
<table>
  <thead>
      <tr>
          <th>檔案數量</th>
          <th>循序時間</th>
          <th>並行時間 (8 workers)</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10</td>
          <td>0.15s</td>
          <td>0.08s</td>
          <td>1.9x</td>
      </tr>
      <tr>
          <td>50</td>
          <td>0.72s</td>
          <td>0.18s</td>
          <td>4.0x</td>
      </tr>
      <tr>
          <td>100</td>
          <td>1.45s</td>
          <td>0.32s</td>
          <td>4.5x</td>
      </tr>
      <tr>
          <td>200</td>
          <td>2.91s</td>
          <td>0.58s</td>
          <td>5.0x</td>
      </tr>
  </tbody>
</table>
<p><strong>觀察</strong>：</p>
<ol>
<li>檔案越多，並行效益越明顯</li>
<li>加速比不會無限增長（受限於 I/O 頻寬和 GIL）</li>
<li>檔案少於 10 個時，並行的額外開銷可能抵消效益</li>
</ol>
<h3 id="完整的效能測試腳本">完整的效能測試腳本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;效能比較測試腳本&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 假設這是 markdown_link_checker 的簡化版</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">class</span> <span class="nc">MarkdownChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s2">&#34;&#34;&#34;模擬檔案檢查（包含 I/O 延遲）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># 模擬一些處理時間</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.01</span><span class="p">)</span>  <span class="c1"># 10ms I/O 延遲</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="s2">&#34;file&#34;</span><span class="p">:</span> <span class="n">file_path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="s2">&#34;lines&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="s2">&#34;chars&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">check_sequential</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">files</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;循序檢查&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">check_parallel</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">files</span><span class="p">:</span> <span class="nb">list</span><span class="p">,</span> <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">8</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;&#34;&#34;並行檢查&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span><span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="n">f</span><span class="p">):</span> <span class="n">f</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量函式執行時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">elapsed</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="c1"># 收集測試檔案</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">test_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;.&#34;</span><span class="p">)</span>  <span class="c1"># 替換為你的目錄</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">test_dir</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))[:</span><span class="mi">100</span><span class="p">]</span>  <span class="c1"># 取前 100 個</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">files</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;需要至少 10 個 .md 檔案來測試&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;測試檔案數量：</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">files</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="c1"># 測試不同 worker 數量</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">效能比較：&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="n">seq_time</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">checker</span><span class="o">.</span><span class="n">check_sequential</span><span class="p">,</span> <span class="n">files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本：</span><span class="si">{</span><span class="n">seq_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="k">for</span> <span class="n">workers</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">16</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">        <span class="n">par_time</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">checker</span><span class="o">.</span><span class="n">check_parallel</span><span class="p">,</span> <span class="n">files</span><span class="p">,</span> <span class="n">workers</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="n">speedup</span> <span class="o">=</span> <span class="n">seq_time</span> <span class="o">/</span> <span class="n">par_time</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行 (</span><span class="si">{</span><span class="n">workers</span><span class="si">:</span><span class="s2">2d</span><span class="si">}</span><span class="s2"> workers)：</span><span class="si">{</span><span class="n">par_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒 (</span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">    <span class="n">main</span><span class="p">()</span></span></span></code></pre></div><hr>
<h2 id="什麼時候不該用並行">什麼時候不該用並行？</h2>
<h3 id="反模式-1檔案數量太少">反模式 1：檔案數量太少</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不好：只有 3 個檔案還用並行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">files</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;a.md&#34;</span><span class="p">,</span> <span class="s2">&#34;b.md&#34;</span><span class="p">,</span> <span class="s2">&#34;c.md&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">check_file</span><span class="p">,</span> <span class="n">files</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 問題：建立執行緒池的開銷可能比省下的時間還多</span></span></span></code></pre></div><p><strong>建議</strong>：檔案少於 5-10 個時，直接循序處理。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">check_files_smart</span><span class="p">(</span><span class="n">files</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;根據檔案數量選擇處理方式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">files</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="c1"># 少量檔案：循序處理</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="n">check_file</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="c1"># 大量檔案：並行處理</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">            <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">check_file</span><span class="p">,</span> <span class="n">files</span><span class="p">))</span></span></span></code></pre></div><h3 id="反模式-2任務之間有依賴">反模式 2：任務之間有依賴</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不好：任務 B 依賴任務 A 的結果</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">process_a</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">return</span> <span class="n">fetch_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">def</span> <span class="nf">process_b</span><span class="p">(</span><span class="n">config</span><span class="p">):</span>  <span class="c1"># 需要 A 的結果</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">return</span> <span class="n">validate</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 強行並行化會出錯或需要額外同步</span></span></span></code></pre></div><h3 id="反模式-3共享可變狀態">反模式 3：共享可變狀態</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不好：多個執行緒修改同一個列表</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">shared_results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">bad_worker</span><span class="p">(</span><span class="n">file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">check_file</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">shared_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>  <span class="c1"># 競爭條件！</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 正確做法：讓 worker 返回結果，由主執行緒收集</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">good_worker</span><span class="p">(</span><span class="n">file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">check_file</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">good_worker</span><span class="p">,</span> <span class="n">files</span><span class="p">))</span></span></span></code></pre></div><h3 id="反模式-4cpu-密集任務用-threadpoolexecutor">反模式 4：CPU 密集任務用 ThreadPoolExecutor</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不好：CPU 密集任務受 GIL 限制</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">compute_heavy</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># ThreadPoolExecutor 對 CPU 密集任務沒有加速效果</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">compute_heavy</span><span class="p">,</span> <span class="p">[</span><span class="mi">10_000_000</span><span class="p">]</span> <span class="o">*</span> <span class="mi">8</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 正確：CPU 密集應該用 ProcessPoolExecutor</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ProcessPoolExecutor</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">with</span> <span class="n">ProcessPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">compute_heavy</span><span class="p">,</span> <span class="p">[</span><span class="mi">10_000_000</span><span class="p">]</span> <span class="o">*</span> <span class="mi">8</span><span class="p">))</span></span></span></code></pre></div><h3 id="快速決策表">快速決策表</h3>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>推薦做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>&lt; 10 個檔案</td>
          <td>循序處理</td>
      </tr>
      <tr>
          <td>10-100 個檔案，I/O 密集</td>
          <td>ThreadPoolExecutor</td>
      </tr>
      <tr>
          <td>&gt; 100 個檔案，I/O 密集</td>
          <td>ThreadPoolExecutor + 進度報告</td>
      </tr>
      <tr>
          <td>CPU 密集計算</td>
          <td>ProcessPoolExecutor</td>
      </tr>
      <tr>
          <td>任務有依賴關係</td>
          <td>重新設計，或用 asyncio</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="思考題">思考題</h2>
<ol>
<li>
<p><strong>為什麼 <code>check_directory_parallel()</code> 中使用 <code>as_completed()</code> 而不是 <code>executor.map()</code>？</strong>
提示：思考兩者在「錯誤處理」和「進度報告」上的差異。</p>
</li>
<li>
<p><strong>如果 <code>check_file()</code> 除了讀檔還會寫入日誌檔，還適合並行化嗎？需要什麼額外措施？</strong>
提示：考慮日誌寫入的執行緒安全性。</p>
</li>
<li>
<p><strong>在 8 核心的機器上，為什麼 I/O 密集任務開 16 個 worker 可能比 8 個更快？</strong>
提示：思考 GIL 和 I/O 等待時間的關係。</p>
</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>
<p><strong>修改 hook_validator.py</strong>：將 <code>validate_all_hooks()</code> 改寫為並行版本，測量效能差異。</p>
</li>
<li>
<p><strong>實作超時機制</strong>：如果單一檔案檢查超過 5 秒，自動跳過並記錄警告。
提示：查看 <code>future.result(timeout=5)</code>。</p>
</li>
<li>
<p><strong>實作批次處理</strong>：當檔案超過 1000 個時，分批處理（每批 100 個），避免記憶體壓力。</p>
</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">案例研究：並行檔案檢查</a> - 完整的實作與測試</li>
<li><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">案例研究：並行 Hook 驗證</a> - 結合 as_completed 與進度報告</li>
<li>入門系列 <a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a> - 複習基礎概念</li>
<li>進階系列 <a href="/blog/python-advanced/04-cpython-internals/gil-threading/" data-link-title="3.4 GIL 與執行緒模型" data-link-desc="深入理解 GIL 的設計與實現">4.3 GIL 與執行緒模型</a> - 深入理解 GIL</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組索引</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/performance-tuning/" data-link-title="8.2 效能調優實戰" data-link-desc="測量、分析、優化的完整流程">效能調優實戰</a></em></p>
]]></content:encoded></item><item><title>案例：並行檔案檢查</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/markdown_link_checker.py&lt;/code>，展示如何用 ThreadPoolExecutor 加速 I/O 密集的檔案檢查任務。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>markdown_link_checker.py&lt;/code> 的 &lt;code>check_directory()&lt;/code> 方法檢查目錄下所有 Markdown 檔案的內部連結：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_directory&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查目錄下所有 Markdown 檔案
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> dir_path: 目錄路徑
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> recursive: 是否遞迴檢查子目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> list[LinkCheckResult]: 所有檔案的檢查結果
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_dir&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">total_links&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">broken_links&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">BrokenLink&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">file&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">line&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">link_text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">link_target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;目錄不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集所有 .md 檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rglob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 循序檢查每個檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">md_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">md_files&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">md_file&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：循序執行，程式碼容易理解&lt;/li>
&lt;li>&lt;strong>結果有序&lt;/strong>：檔案按排序順序處理，結果也按順序返回&lt;/li>
&lt;li>&lt;strong>除錯容易&lt;/strong>：問題發生時，可以精確定位到哪個檔案&lt;/li>
&lt;/ol>
&lt;h3 id="效能瓶頸分析">效能瓶頸分析&lt;/h3>
&lt;p>讓我們分析 &lt;code>check_file()&lt;/code> 方法的執行時間組成：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查單個 Markdown 檔案的連結&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 1. 檢查檔案是否存在（I/O）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 2. 讀取檔案內容（I/O - 主要瓶頸）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read_text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 3. 解析連結（CPU - 很快）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse_markdown_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 4. 過濾內部連結（CPU - 很快）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">internal_links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_filter_internal_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">links&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 5. 檢查每個連結（I/O - 檔案系統檢查）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">broken_links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">link&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">internal_links&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">is_valid&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">suggestion&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_check_link&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">link&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">is_valid&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">broken_links&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>時間分布估計&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">操作 | 類型 | 每檔案耗時
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">-----------------|-------|----------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">read_text() | I/O | 1-5 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">parse_links() | CPU | 0.1 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">filter_links() | CPU | 0.01 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">check_link() x N | I/O | N * 0.5 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">-----------------|-------|----------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">總計（10 連結） | | ~7 ms&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>對於 100 個檔案的專案：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/markdown_link_checker.py</code>，展示如何用 ThreadPoolExecutor 加速 I/O 密集的檔案檢查任務。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>markdown_link_checker.py</code> 的 <code>check_directory()</code> 方法檢查目錄下所有 Markdown 檔案的內部連結：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    檢查目錄下所有 Markdown 檔案
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        dir_path: 目錄路徑
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        recursive: 是否遞迴檢查子目錄
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        list[LinkCheckResult]: 所有檔案的檢查結果
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">                <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">                <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">                <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">                        <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                        <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;目錄不存在: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="c1"># 循序檢查每個檔案</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：循序執行，程式碼容易理解</li>
<li><strong>結果有序</strong>：檔案按排序順序處理，結果也按順序返回</li>
<li><strong>除錯容易</strong>：問題發生時，可以精確定位到哪個檔案</li>
</ol>
<h3 id="效能瓶頸分析">效能瓶頸分析</h3>
<p>讓我們分析 <code>check_file()</code> 方法的執行時間組成：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查單個 Markdown 檔案的連結&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">file_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1"># 1. 檢查檔案是否存在（I/O）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># 2. 讀取檔案內容（I/O - 主要瓶頸）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># 3. 解析連結（CPU - 很快）</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 4. 過濾內部連結（CPU - 很快）</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">internal_links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_filter_internal_links</span><span class="p">(</span><span class="n">links</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># 5. 檢查每個連結（I/O - 檔案系統檢查）</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">broken_links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">internal_links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">is_valid</span><span class="p">,</span> <span class="n">suggestion</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_check_link</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">file_path</span><span class="o">.</span><span class="n">parent</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">broken_links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span></span></span></code></pre></div><p><strong>時間分布估計</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">操作              | 類型  | 每檔案耗時
</span></span><span class="line"><span class="ln">2</span><span class="cl">-----------------|-------|----------
</span></span><span class="line"><span class="ln">3</span><span class="cl">read_text()      | I/O   | 1-5 ms
</span></span><span class="line"><span class="ln">4</span><span class="cl">parse_links()    | CPU   | 0.1 ms
</span></span><span class="line"><span class="ln">5</span><span class="cl">filter_links()   | CPU   | 0.01 ms
</span></span><span class="line"><span class="ln">6</span><span class="cl">check_link() x N | I/O   | N * 0.5 ms
</span></span><span class="line"><span class="ln">7</span><span class="cl">-----------------|-------|----------
</span></span><span class="line"><span class="ln">8</span><span class="cl">總計（10 連結）  |       | ~7 ms</span></span></code></pre></div><p>對於 100 個檔案的專案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 循序執行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">total_time</span> <span class="o">=</span> <span class="mi">100</span> <span class="o">*</span> <span class="mi">7</span><span class="n">ms</span> <span class="o">=</span> <span class="mi">700</span><span class="n">ms</span> <span class="o">=</span> <span class="mf">0.7</span> <span class="n">秒</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 這看起來不長，但如果：</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># - 檔案更多（500+ 個）</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># - 每個檔案連結更多</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># - 網路檔案系統（NFS）</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 時間會快速增長</span></span></span></code></pre></div><h4 id="為什麼適合並行化">為什麼適合並行化？</h4>
<ol>
<li><strong>I/O 密集</strong>：大部分時間花在檔案讀取和存在性檢查</li>
<li><strong>任務獨立</strong>：每個檔案的檢查互不依賴</li>
<li><strong>無共享狀態</strong>：不需要同步機制</li>
</ol>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>提升效能</strong>：利用並行化加速 I/O 操作</li>
<li><strong>保持 API 相容</strong>：不改變方法簽名和返回值</li>
<li><strong>可配置</strong>：允許調整並行度</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1識別獨立任務">步驟 1：識別獨立任務</h4>
<p>每個檔案的檢查是完全獨立的：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 這些操作可以同時執行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">result_1</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="s2">&#34;doc1.md&#34;</span><span class="p">)</span>  <span class="c1"># 獨立</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">result_2</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="s2">&#34;doc2.md&#34;</span><span class="p">)</span>  <span class="c1"># 獨立</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">result_3</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="s2">&#34;doc3.md&#34;</span><span class="p">)</span>  <span class="c1"># 獨立</span></span></span></code></pre></div><h4 id="步驟-2使用-threadpoolexecutor">步驟 2：使用 ThreadPoolExecutor</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_parallel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    並行檢查目錄下所有 Markdown 檔案
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        dir_path: 目錄路徑
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        recursive: 是否遞迴檢查子目錄
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        max_workers: 最大工作執行緒數，預設為 CPU 核心數
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        list[LinkCheckResult]: 所有檔案的檢查結果
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_create_error_result</span><span class="p">(</span><span class="n">dir_path</span><span class="p">,</span> <span class="s2">&#34;目錄不存在&#34;</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">pattern</span> <span class="o">=</span> <span class="s2">&#34;**/*.md&#34;</span> <span class="k">if</span> <span class="n">recursive</span> <span class="k">else</span> <span class="s2">&#34;*.md&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">recursive</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                      <span class="k">else</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="c1"># 使用 ThreadPoolExecutor 並行處理</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">future_to_file</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)):</span> <span class="n">md_file</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="c1"># 收集結果</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="c1"># 按檔案路徑排序（保持一致的輸出順序）</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">results</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">r</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h4 id="步驟-3選擇-max_workers">步驟 3：選擇 max_workers</h4>
<p><code>max_workers</code> 的選擇影響效能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 預設值：min(32, os.cpu_count() + 4)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 這是 Python 3.8+ 的預設行為</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 對於 I/O 密集任務，可以設定更高</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">get_optimal_workers</span><span class="p">(</span><span class="n">file_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    根據檔案數量計算最佳工作執行緒數
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    經驗法則：
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 檔案數 &lt; 10: 使用檔案數
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 檔案數 &gt;= 10: 使用 CPU 核心數 * 2，但不超過 32
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">cpu_count</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">4</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">if</span> <span class="n">file_count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="n">file_count</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="n">cpu_count</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">file_count</span><span class="p">)</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">並行 Markdown 連結檢查器
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">基於 markdown_link_checker.py，展示如何用 ThreadPoolExecutor 加速檔案檢查。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">class</span> <span class="nc">BrokenLink</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="s2">&#34;&#34;&#34;失效連結描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">file</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">link_text</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">link_target</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="k">class</span> <span class="nc">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個檔案的連結檢查結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="n">total_links</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="n">broken_links</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">BrokenLink</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="n">is_valid</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">broken_links</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="k">class</span> <span class="nc">ParallelMarkdownLinkChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">    並行 Markdown 連結檢查器
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    相較於原版的改進：
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">    - check_directory() 使用 ThreadPoolExecutor 並行處理
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">    - 支援自訂 max_workers
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">    - 保持 API 相容性
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="n">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="n">EXTERNAL_PATTERNS</span> <span class="o">=</span> <span class="p">[</span><span class="sa">r</span><span class="s1">&#39;^https?://&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;^mailto:&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;^tel:&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;^ftp://&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="c1"># ===== 核心方法 =====</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">        檢查單個 Markdown 檔案的連結
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">        這個方法是執行緒安全的，可以並行呼叫。
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="n">file_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案不存在: </span><span class="si">{</span><span class="n">file_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">                <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="n">internal_links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_filter_internal_links</span><span class="p">(</span><span class="n">links</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">broken_links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">internal_links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="n">is_valid</span><span class="p">,</span> <span class="n">suggestion</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_check_link</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">],</span> <span class="n">file_path</span><span class="o">.</span><span class="n">parent</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">                <span class="n">broken_links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                        <span class="n">line</span><span class="o">=</span><span class="n">link</span><span class="p">[</span><span class="s2">&#34;line&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="n">link</span><span class="p">[</span><span class="s2">&#34;text&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                        <span class="n">link_target</span><span class="o">=</span><span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="n">suggestion</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="n">total_links</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">internal_links</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">            <span class="n">broken_links</span><span class="o">=</span><span class="n">broken_links</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="k">def</span> <span class="nf">check_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="n">max_workers</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="s2">        並行檢查目錄下所有 Markdown 檔案
</span></span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="s2">            dir_path: 目錄路徑
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="s2">            recursive: 是否遞迴檢查子目錄
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">            max_workers: 最大工作執行緒數，None 表示使用預設值
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">            list[LinkCheckResult]: 所有檔案的檢查結果（按路徑排序）
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">                    <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">                    <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">                    <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">                        <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">                            <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">                            <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">                            <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;目錄不存在: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="c1"># 計算最佳工作執行緒數</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="k">if</span> <span class="n">max_workers</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="n">max_workers</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_optimal_workers</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">md_files</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">
</span></span><span class="line"><span class="ln">163</span><span class="cl">        <span class="c1"># 並行處理</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">        <span class="n">results</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">            <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">            <span class="n">future_to_file</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">                <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">                <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">            <span class="c1"># 收集結果（as_completed 提供最快的回應）</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">            <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">                    <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">                    <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">                <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">                    <span class="c1"># 處理意外錯誤</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">                    <span class="n">md_file</span> <span class="o">=</span> <span class="n">future_to_file</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">                    <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">                        <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">                            <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">                            <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">                            <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">                                <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">                                    <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">                                    <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">                                    <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檢查失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">                                <span class="p">)</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">                            <span class="p">]</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="c1"># 排序以保持一致的輸出順序</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">r</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">
</span></span><span class="line"><span class="ln">198</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="c1"># ===== 循序版本（用於比較）=====</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">
</span></span><span class="line"><span class="ln">202</span><span class="cl">    <span class="k">def</span> <span class="nf">check_directory_sequential</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="s2">&#34;&#34;&#34;循序版本，用於效能比較&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">        <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">                    <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">                    <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">                    <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">                        <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">                            <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">                            <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">                            <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;目錄不存在: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">
</span></span><span class="line"><span class="ln">230</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">
</span></span><span class="line"><span class="ln">234</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="c1"># ===== 私有方法 =====</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="k">return</span> <span class="n">p</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">()</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">p</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">
</span></span><span class="line"><span class="ln">242</span><span class="cl">    <span class="k">def</span> <span class="nf">_parse_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">
</span></span><span class="line"><span class="ln">246</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">),</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">
</span></span><span class="line"><span class="ln">251</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">
</span></span><span class="line"><span class="ln">254</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">
</span></span><span class="line"><span class="ln">261</span><span class="cl">        <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">
</span></span><span class="line"><span class="ln">263</span><span class="cl">    <span class="k">def</span> <span class="nf">_filter_internal_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">links</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">        <span class="n">internal</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">            <span class="n">target</span> <span class="o">=</span> <span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">            <span class="k">if</span> <span class="n">target</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;#&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">            <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">EXTERNAL_PATTERNS</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">            <span class="n">internal</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">link</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">        <span class="k">return</span> <span class="n">internal</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_link</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">        <span class="n">target</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">        <span class="n">base_dir</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="n">target_path</span> <span class="o">=</span> <span class="n">target</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;#&#34;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">target_path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">
</span></span><span class="line"><span class="ln">283</span><span class="cl">        <span class="n">resolved</span> <span class="o">=</span> <span class="p">(</span><span class="n">base_dir</span> <span class="o">/</span> <span class="n">target_path</span><span class="p">)</span><span class="o">.</span><span class="n">resolve</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="k">if</span> <span class="n">resolved</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;檔案不存在: </span><span class="si">{</span><span class="n">target_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">
</span></span><span class="line"><span class="ln">289</span><span class="cl">    <span class="k">def</span> <span class="nf">_get_optimal_workers</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">        <span class="s2">&#34;&#34;&#34;計算最佳工作執行緒數&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">        <span class="n">cpu_count</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">4</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">        <span class="k">if</span> <span class="n">file_count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">            <span class="k">return</span> <span class="n">file_count</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">        <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="n">cpu_count</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">file_count</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">
</span></span><span class="line"><span class="ln">296</span><span class="cl"><span class="c1"># ===== 效能測量工具 =====</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">
</span></span><span class="line"><span class="ln">298</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_checker</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">303</span><span class="cl"><span class="s2">    比較循序與並行版本的效能
</span></span></span><span class="line"><span class="ln">304</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">305</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">306</span><span class="cl"><span class="s2">        dir_path: 要檢查的目錄
</span></span></span><span class="line"><span class="ln">307</span><span class="cl"><span class="s2">        iterations: 執行次數（取平均）
</span></span></span><span class="line"><span class="ln">308</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">309</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">310</span><span class="cl"><span class="s2">        dict: {&#39;sequential&#39;: 秒數, &#39;parallel&#39;: 秒數, &#39;speedup&#39;: 加速比}
</span></span></span><span class="line"><span class="ln">311</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">
</span></span><span class="line"><span class="ln">314</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">ParallelMarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="c1"># 預熱（讓檔案系統快取生效）</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">
</span></span><span class="line"><span class="ln">319</span><span class="cl">    <span class="c1"># 測量循序版本</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">    <span class="n">seq_times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">        <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_sequential</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">        <span class="n">seq_times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">
</span></span><span class="line"><span class="ln">326</span><span class="cl">    <span class="c1"># 測量並行版本</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">    <span class="n">par_times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">        <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl">        <span class="n">par_times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">
</span></span><span class="line"><span class="ln">333</span><span class="cl">    <span class="n">seq_avg</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">seq_times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">seq_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="n">par_avg</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">par_times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">par_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">
</span></span><span class="line"><span class="ln">336</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">        <span class="s2">&#34;sequential&#34;</span><span class="p">:</span> <span class="n">seq_avg</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">        <span class="s2">&#34;parallel&#34;</span><span class="p">:</span> <span class="n">par_avg</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">        <span class="s2">&#34;speedup&#34;</span><span class="p">:</span> <span class="n">seq_avg</span> <span class="o">/</span> <span class="n">par_avg</span> <span class="k">if</span> <span class="n">par_avg</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">
</span></span><span class="line"><span class="ln">342</span><span class="cl"><span class="c1"># ===== 示範 =====</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">
</span></span><span class="line"><span class="ln">347</span><span class="cl">    <span class="c1"># 預設檢查當前目錄</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="n">target_dir</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">else</span> <span class="s2">&#34;.&#34;</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">
</span></span><span class="line"><span class="ln">350</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;=== 並行 Markdown 連結檢查示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目標目錄: </span><span class="si">{</span><span class="n">target_dir</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">352</span><span class="cl">
</span></span><span class="line"><span class="ln">353</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">ParallelMarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">354</span><span class="cl">
</span></span><span class="line"><span class="ln">355</span><span class="cl">    <span class="c1"># 執行檢查</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">target_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">357</span><span class="cl">
</span></span><span class="line"><span class="ln">358</span><span class="cl">    <span class="c1"># 統計</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">    <span class="n">total_files</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">    <span class="n">total_links</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">total_links</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">    <span class="n">broken_count</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">broken_links</span><span class="p">)</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">    <span class="n">invalid_files</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">r</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">
</span></span><span class="line"><span class="ln">364</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;檔案數: </span><span class="si">{</span><span class="n">total_files</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">365</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;連結數: </span><span class="si">{</span><span class="n">total_links</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;失效連結: </span><span class="si">{</span><span class="n">broken_count</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">367</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;有問題的檔案: </span><span class="si">{</span><span class="n">invalid_files</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">
</span></span><span class="line"><span class="ln">369</span><span class="cl">    <span class="c1"># 顯示失效連結</span>
</span></span><span class="line"><span class="ln">370</span><span class="cl">    <span class="k">if</span> <span class="n">broken_count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">失效連結詳情:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">372</span><span class="cl">        <span class="k">for</span> <span class="n">result</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">373</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">374</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">  </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">file_path</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">375</span><span class="cl">                <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">broken_links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">376</span><span class="cl">                    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    Line </span><span class="si">{</span><span class="n">link</span><span class="o">.</span><span class="n">line</span><span class="si">}</span><span class="s2">: [</span><span class="si">{</span><span class="n">link</span><span class="o">.</span><span class="n">link_text</span><span class="si">}</span><span class="s2">](/python-advanced/08-practical-optimization/case-studies/parallel-file-check/</span><span class="si">{</span><span class="n">link</span><span class="o">.</span><span class="n">link_target</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">377</span><span class="cl">
</span></span><span class="line"><span class="ln">378</span><span class="cl">    <span class="c1"># 效能比較</span>
</span></span><span class="line"><span class="ln">379</span><span class="cl">    <span class="k">if</span> <span class="n">total_files</span> <span class="o">&gt;=</span> <span class="mi">5</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">380</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== 效能比較 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">381</span><span class="cl">        <span class="n">benchmark</span> <span class="o">=</span> <span class="n">benchmark_checker</span><span class="p">(</span><span class="n">target_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">382</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本: </span><span class="si">{</span><span class="n">benchmark</span><span class="p">[</span><span class="s1">&#39;sequential&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">383</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行版本: </span><span class="si">{</span><span class="n">benchmark</span><span class="p">[</span><span class="s1">&#39;parallel&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">384</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比: </span><span class="si">{</span><span class="n">benchmark</span><span class="p">[</span><span class="s1">&#39;speedup&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="效能測量">效能測量</h3>
<p>使用 <code>timeit</code> 比較前後效能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">parallel_link_checker</span> <span class="kn">import</span> <span class="n">ParallelMarkdownLinkChecker</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">measure_performance</span><span class="p">(</span><span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">num_runs</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量並比較循序與並行版本的效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">ParallelMarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 循序版本</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">seq_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_sequential</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">)</span> <span class="o">/</span> <span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># 並行版本</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">par_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="p">)</span> <span class="o">/</span> <span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目錄: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本: </span><span class="si">{</span><span class="n">seq_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行版本: </span><span class="si">{</span><span class="n">par_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比: </span><span class="si">{</span><span class="n">seq_time</span> <span class="o">/</span> <span class="n">par_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"># 實際測試結果（範例）</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># 目錄: ./docs （50 個 .md 檔案）</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"># 循序版本: 0.3521 秒</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># 並行版本: 0.0892 秒</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"># 加速比: 3.95x</span></span></span></code></pre></div><p><strong>不同規模的預期加速比</strong>：</p>
<table>
  <thead>
      <tr>
          <th>檔案數</th>
          <th>循序時間</th>
          <th>並行時間</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10</td>
          <td>70 ms</td>
          <td>25 ms</td>
          <td>2.8x</td>
      </tr>
      <tr>
          <td>50</td>
          <td>350 ms</td>
          <td>90 ms</td>
          <td>3.9x</td>
      </tr>
      <tr>
          <td>100</td>
          <td>700 ms</td>
          <td>160 ms</td>
          <td>4.4x</td>
      </tr>
      <tr>
          <td>500</td>
          <td>3.5 s</td>
          <td>750 ms</td>
          <td>4.7x</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>注意：實際加速比取決於檔案大小、連結數量、磁碟速度等因素。</p></blockquote>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>循序版本</th>
          <th>並行版本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>效能</td>
          <td>較慢，線性增長</td>
          <td>快 3-5 倍</td>
      </tr>
      <tr>
          <td>複雜度</td>
          <td>簡單</td>
          <td>需要理解執行緒池</td>
      </tr>
      <tr>
          <td>除錯</td>
          <td>容易</td>
          <td>需要注意執行緒安全</td>
      </tr>
      <tr>
          <td>記憶體</td>
          <td>較低</td>
          <td>較高（執行緒開銷）</td>
      </tr>
      <tr>
          <td>結果順序</td>
          <td>保證有序</td>
          <td>需要額外排序</td>
      </tr>
      <tr>
          <td>錯誤處理</td>
          <td>直接</td>
          <td>需要處理 Future 例外</td>
      </tr>
  </tbody>
</table>
<h3 id="執行緒安全考量">執行緒安全考量</h3>
<p><code>check_file()</code> 方法是執行緒安全的，因為：</p>
<ol>
<li><strong>無共享狀態</strong>：每次呼叫都獨立處理一個檔案</li>
<li><strong>唯讀操作</strong>：只讀取檔案，不修改</li>
<li><strong>獨立返回值</strong>：每個呼叫返回獨立的 <code>LinkCheckResult</code></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 這是安全的</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># 所有變數都是區域變數</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">file_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>  <span class="c1"># 新物件</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">()</span>            <span class="c1"># 區域變數</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>         <span class="c1"># 區域變數</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>                <span class="c1"># 新物件</span></span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合使用">適合使用</h3>
<ul>
<li><strong>多檔案處理</strong>：需要處理大量獨立檔案</li>
<li><strong>I/O 密集</strong>：主要時間花在檔案讀寫</li>
<li><strong>任務獨立</strong>：每個任務不依賴其他任務的結果</li>
<li><strong>可接受亂序</strong>：或願意在最後排序</li>
</ul>
<h3 id="不建議使用">不建議使用</h3>
<ul>
<li><strong>檔案很少</strong>：少於 5 個檔案，並行開銷可能大於收益</li>
<li><strong>CPU 密集</strong>：如果主要時間花在計算，應考慮 ProcessPoolExecutor</li>
<li><strong>有依賴關係</strong>：後續檔案依賴前面檔案的結果</li>
<li><strong>記憶體受限</strong>：並行版本會同時載入多個檔案</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="練習-1加入進度回報">練習 1：加入進度回報</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">callback</span><span class="p">:</span> <span class="n">callable</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    並行檢查，並在每個檔案完成時呼叫 callback
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    callback 簽名: callback(completed: int, total: int, result: LinkCheckResult)
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：使用 as_completed() 在每個任務完成時觸發回報
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-2支援取消">練習 2：支援取消</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_cancellable</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">cancel_event</span><span class="p">:</span> <span class="n">threading</span><span class="o">.</span><span class="n">Event</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    可取消的並行檢查
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    當 cancel_event.is_set() 時，停止提交新任務並返回已完成的結果
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：在迴圈中檢查 cancel_event
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="練習-3批次處理大型目錄">練習 3：批次處理大型目錄</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_batched</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">batch_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    分批處理大型目錄
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    避免一次提交太多任務導致記憶體問題
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：將檔案列表分成多個批次，依序處理每批
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-4加入重試機制">練習 4：加入重試機制</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_file_with_retry</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">max_retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    帶重試的檔案檢查
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    當檔案被鎖定或暫時不可用時自動重試
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：捕捉特定例外，使用指數退避
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="練習-5實作並行度自動調整">練習 5：實作並行度自動調整</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">AdaptiveParallelChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    自動調整並行度的檢查器
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    根據系統負載和檢查速度動態調整 max_workers
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    功能：
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 初始使用保守的 max_workers
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 如果任務完成很快，增加 max_workers
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 如果系統負載高，減少 max_workers
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 記錄最佳 max_workers 供下次使用
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html">concurrent.futures 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor">ThreadPoolExecutor 使用指南</a></li>
<li><a href="https://superfastpython.com/threadpoolexecutor-map-vs-submit/">as_completed vs map 的選擇</a></li>
<li>入門系列：<a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a></li>
</ul>
<hr>
<p>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證</a></p>
]]></content:encoded></item><item><title>案例：並行 Hook 驗證</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的 &lt;code>validate_all_hooks()&lt;/code> 方法，展示如何使用 &lt;code>ThreadPoolExecutor&lt;/code> 配合 &lt;code>submit()&lt;/code> + &lt;code>as_completed()&lt;/code> 實現並行驗證，並加入即時進度報告功能。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">案例：並行檔案檢查&lt;/a>（使用 &lt;code>map()&lt;/code> 的基本並行模式）&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 的 &lt;code>validate_all_hooks()&lt;/code> 方法需要驗證多個 Hook 檔案：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">List&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationIssue&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證問題描述&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="c1"># &amp;#34;error&amp;#34; | &amp;#34;warning&amp;#34; | &amp;#34;info&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl"> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;單個 Hook 的驗證結果&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl"> &lt;span class="n">is_compliant&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">__post_init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_compliant&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl"> &lt;span class="n">issue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">level&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">issue&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">issues&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證單個 Hook 檔案
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證項目：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 命名規範檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 共用模組導入檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 輸出格式檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 測試存在性檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hook 檔案不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 讀取檔案並執行各項檢查&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;無法讀取 Hook 檔案: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">issues&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_all_hooks&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl">&lt;span class="s2"> 同步版本：依序驗證所有 Hook 檔案
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">hooks_dir&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="n">hooks_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;hooks&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl"> &lt;span class="n">hooks_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">hooks_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_dir&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hook 目錄不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 找出所有 .py 檔案並依序驗證&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">hook_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.py&amp;#34;&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">hook_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;_&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_file&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：循序執行，易於理解和除錯&lt;/li>
&lt;li>&lt;strong>結果有序&lt;/strong>：按檔案名稱排序，輸出一致&lt;/li>
&lt;li>&lt;strong>錯誤處理明確&lt;/strong>：每個驗證結果立即可用&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當 Hook 數量增加時：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的 <code>validate_all_hooks()</code> 方法，展示如何使用 <code>ThreadPoolExecutor</code> 配合 <code>submit()</code> + <code>as_completed()</code> 實現並行驗證，並加入即時進度報告功能。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a></li>
<li><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">案例：並行檔案檢查</a>（使用 <code>map()</code> 的基本並行模式）</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 的 <code>validate_all_hooks()</code> 方法需要驗證多個 Hook 檔案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證問題描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個 Hook 的驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="s2">        驗證單個 Hook 檔案
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">        驗證項目：
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">        - 命名規範檢查
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        - 共用模組導入檢查
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        - 輸出格式檢查
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        - 測試存在性檢查
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="n">hook_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 檔案不存在: </span><span class="si">{</span><span class="n">hook_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="c1"># 讀取檔案並執行各項檢查</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">hook_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">                <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取 Hook 檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_lib_imports</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_output_format</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_test_exists</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_all_hooks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">        同步版本：依序驗證所有 Hook 檔案
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">if</span> <span class="n">hooks_dir</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="n">hooks_dir</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">hooks_dir</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hooks_dir</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                        <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">                            <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">                            <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 目錄不存在: </span><span class="si">{</span><span class="n">hooks_dir</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="c1"># 找出所有 .py 檔案並依序驗證</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="k">if</span> <span class="n">hook_file</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>簡單直覺</strong>：循序執行，易於理解和除錯</li>
<li><strong>結果有序</strong>：按檔案名稱排序，輸出一致</li>
<li><strong>錯誤處理明確</strong>：每個驗證結果立即可用</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當 Hook 數量增加時：</p>
<ul>
<li><strong>執行時間線性增長</strong>：20 個 Hook，每個 0.1 秒 = 2 秒</li>
<li><strong>無法利用 I/O 等待時間</strong>：讀取檔案時 CPU 閒置</li>
<li><strong>使用者體驗差</strong>：大量 Hook 時沒有進度回饋</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_sync</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量同步版本的執行時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 20 個 Hook，每個 0.1 秒</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 總計：20 * 0.1 = 2.0 秒</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="map-vs-submit--as_completed">map() vs submit() + as_completed()</h3>
<p>在「並行檔案檢查」案例中，我們使用 <code>executor.map()</code> 實現並行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_map</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用 map() 的並行版本
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    特點：
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 結果按輸入順序返回
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 必須等所有任務完成才能取得結果
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 無法即時報告進度
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><p><strong><code>map()</code> 的限制</strong>：</p>
<ol>
<li><strong>無法即時取得結果</strong>：必須等待所有任務完成</li>
<li><strong>無法追蹤進度</strong>：不知道哪些任務已完成</li>
<li><strong>異常處理受限</strong>：遇到第一個異常就停止迭代</li>
</ol>
<p><strong><code>submit()</code> + <code>as_completed()</code> 的優勢</strong>：</p>
<ol>
<li><strong>即時取得完成的結果</strong>：任務完成就能處理</li>
<li><strong>支援進度報告</strong>：可以計算已完成數量</li>
<li><strong>更靈活的異常處理</strong>：可以逐一處理每個任務的異常</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span><span class="p">,</span> <span class="n">Future</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_async</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    使用 submit() + as_completed() 的並行版本
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    特點：
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 結果按完成順序返回
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 可以即時報告進度
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 更完善的錯誤處理
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># 依完成順序處理結果</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="c1"># 個別任務失敗不影響其他任務</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="實作進度報告">實作進度報告</h3>
<p><code>as_completed()</code> 的核心優勢是支援即時進度報告：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span><span class="p">,</span> <span class="n">Future</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">progress_callback</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="kc">None</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    帶進度報告的並行驗證
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        hook_files: Hook 檔案列表
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        progress_callback: 進度回調函式
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">            - 參數: (已完成數, 總數, 當前檔案名)
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        list[ValidationResult]: 驗證結果列表
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># 提交所有任務，記錄 Future 到路徑的映射</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="c1"># 依完成順序處理結果</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">for</span> <span class="n">completed_count</span><span class="p">,</span> <span class="n">future</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="n">start</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="c1"># 呼叫進度回調</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="k">if</span> <span class="n">progress_callback</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">                <span class="n">progress_callback</span><span class="p">(</span><span class="n">completed_count</span><span class="p">,</span> <span class="n">total</span><span class="p">,</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="k">def</span> <span class="nf">print_progress</span><span class="p">(</span><span class="n">completed</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">    <span class="s2">&#34;&#34;&#34;簡單的進度顯示&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="n">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="n">bar_length</span> <span class="o">=</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="n">filled</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="n">bar</span> <span class="o">=</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="n">filled</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="c1"># \r 回到行首覆蓋顯示</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">bar</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">percentage</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">%) - </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">
</span></span><span class="line"><span class="ln">69</span><span class="cl">    <span class="k">if</span> <span class="n">completed</span> <span class="o">==</span> <span class="n">total</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>  <span class="c1"># 完成後換行</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="k">def</span> <span class="nf">demo_progress</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="n">hooks_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;.claude/hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">    <span class="n">hook_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">
</span></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;開始驗證 Hook 檔案...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">validate_all_hooks_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="n">hook_files</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">        <span class="n">progress_callback</span><span class="o">=</span><span class="n">print_progress</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">
</span></span><span class="line"><span class="ln">83</span><span class="cl">    <span class="c1"># 統計結果</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">    <span class="n">compliant</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">合規: </span><span class="si">{</span><span class="n">compliant</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>進度報告的變體</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ProgressInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;進度資訊&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">completed</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">total</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">current_file</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">elapsed_seconds</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">estimated_remaining</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">ProgressTracker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;進度追蹤器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">total</span> <span class="o">=</span> <span class="n">total</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">completed</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">start_time</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ProgressInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s2">&#34;&#34;&#34;更新進度並返回資訊&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">completed</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">start_time</span><span class="p">)</span><span class="o">.</span><span class="n">total_seconds</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># 估算剩餘時間</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">avg_time</span> <span class="o">=</span> <span class="n">elapsed</span> <span class="o">/</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="n">remaining</span> <span class="o">=</span> <span class="n">avg_time</span> <span class="o">*</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">total</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">remaining</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">return</span> <span class="n">ProgressInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="n">completed</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">completed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">total</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">total</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="n">current_file</span><span class="o">=</span><span class="n">filename</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="n">elapsed_seconds</span><span class="o">=</span><span class="n">elapsed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">estimated_remaining</span><span class="o">=</span><span class="n">remaining</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_rich_progress</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    帶詳細進度資訊的驗證
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">    顯示：完成數、百分比、已用時間、預估剩餘時間
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">tracker</span> <span class="o">=</span> <span class="n">ProgressTracker</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">future_to_path</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">
</span></span><span class="line"><span class="ln">73</span><span class="cl">            <span class="c1"># 更新並顯示進度</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">            <span class="n">info</span> <span class="o">=</span> <span class="n">tracker</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">            <span class="n">print_rich_progress</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">
</span></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">
</span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="k">def</span> <span class="nf">print_rich_progress</span><span class="p">(</span><span class="n">info</span><span class="p">:</span> <span class="n">ProgressInfo</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">    <span class="s2">&#34;&#34;&#34;顯示詳細進度&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">    <span class="n">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="n">info</span><span class="o">.</span><span class="n">completed</span> <span class="o">/</span> <span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">    <span class="n">bar_length</span> <span class="o">=</span> <span class="mi">20</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl">    <span class="n">filled</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">info</span><span class="o">.</span><span class="n">completed</span> <span class="o">/</span> <span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">    <span class="n">bar</span> <span class="o">=</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="n">filled</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">
</span></span><span class="line"><span class="ln">86</span><span class="cl">    <span class="n">elapsed_str</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">elapsed_seconds</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">87</span><span class="cl">    <span class="n">remaining_str</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">estimated_remaining</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">88</span><span class="cl">
</span></span><span class="line"><span class="ln">89</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">90</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">bar</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="si">}</span><span class="s2"> &#34;</span>
</span></span><span class="line"><span class="ln">91</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;(</span><span class="si">{</span><span class="n">percentage</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">%) | &#34;</span>
</span></span><span class="line"><span class="ln">92</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;已用: </span><span class="si">{</span><span class="n">elapsed_str</span><span class="si">}</span><span class="s2"> | &#34;</span>
</span></span><span class="line"><span class="ln">93</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;剩餘: </span><span class="si">{</span><span class="n">remaining_str</span><span class="si">}</span><span class="s2"> | &#34;</span>
</span></span><span class="line"><span class="ln">94</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">current_file</span><span class="p">[:</span><span class="mi">20</span><span class="p">]</span><span class="si">:</span><span class="s2">&lt;20</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">95</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">96</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">97</span><span class="cl">
</span></span><span class="line"><span class="ln">98</span><span class="cl">    <span class="k">if</span> <span class="n">info</span><span class="o">.</span><span class="n">completed</span> <span class="o">==</span> <span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">99</span><span class="cl">        <span class="nb">print</span><span class="p">()</span></span></span></code></pre></div><h3 id="錯誤處理策略">錯誤處理策略</h3>
<p><code>submit()</code> + <code>as_completed()</code> 提供更細緻的錯誤處理：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">    <span class="n">ThreadPoolExecutor</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">    <span class="n">as_completed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">    <span class="n">Future</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">    <span class="ne">TimeoutError</span> <span class="k">as</span> <span class="n">FuturesTimeoutError</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationStatus</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">SUCCESS</span> <span class="o">=</span> <span class="s2">&#34;success&#34;</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">FAILED</span> <span class="o">=</span> <span class="s2">&#34;failed&#34;</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">TIMEOUT</span> <span class="o">=</span> <span class="s2">&#34;timeout&#34;</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">CANCELLED</span> <span class="o">=</span> <span class="s2">&#34;cancelled&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="k">class</span> <span class="nc">DetailedResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="s2">&#34;&#34;&#34;包含狀態的詳細結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">status</span><span class="p">:</span> <span class="n">ValidationStatus</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">result</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_error_handling</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">timeout_per_file</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">5.0</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">    帶完善錯誤處理的並行驗證
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="s2">    處理的錯誤類型：
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">    - 驗證邏輯錯誤
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">    - 單一任務超時
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">    - 任務被取消
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        hook_files: Hook 檔案列表
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        timeout_per_file: 單一檔案的超時秒數
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">        list[DetailedResult]: 包含狀態的詳細結果
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="n">detailed_results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">                <span class="c1"># 設定單一結果的超時</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">timeout_per_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">                    <span class="n">result</span><span class="o">=</span><span class="n">result</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="k">except</span> <span class="n">FuturesTimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證超時 (</span><span class="si">{</span><span class="n">timeout_per_file</span><span class="si">}</span><span class="s2">s)&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">FAILED</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">return</span> <span class="n">detailed_results</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="k">def</span> <span class="nf">summarize_results</span><span class="p">(</span><span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="s2">&#34;&#34;&#34;彙總驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="n">summary</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="s2">&#34;total&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="s2">&#34;success&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="s2">&#34;failed&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="s2">&#34;timeout&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="s2">&#34;compliant&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="s2">&#34;non_compliant&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="s2">&#34;errors&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">ValidationStatus</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;success&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">            <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">result</span> <span class="ow">and</span> <span class="n">r</span><span class="o">.</span><span class="n">result</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">                <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;compliant&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">                <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;non_compliant&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">elif</span> <span class="n">r</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">ValidationStatus</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;timeout&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;errors&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;failed&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;errors&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="k">return</span> <span class="n">summary</span></span></span></code></pre></div><p><strong>錯誤處理模式比較</strong>：</p>
<table>
  <thead>
      <tr>
          <th>模式</th>
          <th><code>map()</code></th>
          <th><code>as_completed()</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>異常傳播</td>
          <td>第一個異常就停止</td>
          <td>可逐一處理</td>
      </tr>
      <tr>
          <td>超時控制</td>
          <td>只能設定全域超時</td>
          <td>可設定單一任務超時</td>
      </tr>
      <tr>
          <td>取消處理</td>
          <td>較難實現</td>
          <td>可以取消個別任務</td>
      </tr>
      <tr>
          <td>部分結果</td>
          <td>異常後無法取得</td>
          <td>已完成的結果仍可取得</td>
      </tr>
  </tbody>
</table>
<h2 id="完整程式碼">完整程式碼</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">並行 Hook 驗證工具 - 完整範例
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 ThreadPoolExecutor + as_completed 實現：
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">- 並行驗證多個 Hook 檔案
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">- 即時進度報告
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">- 完善的錯誤處理
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">ThreadPoolExecutor</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">as_completed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">Future</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="ne">TimeoutError</span> <span class="k">as</span> <span class="n">FuturesTimeoutError</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="c1"># ===== 資料結構 =====</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證問題描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個 Hook 的驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationStatus</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="n">SUCCESS</span> <span class="o">=</span> <span class="s2">&#34;success&#34;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="n">FAILED</span> <span class="o">=</span> <span class="s2">&#34;failed&#34;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="n">TIMEOUT</span> <span class="o">=</span> <span class="s2">&#34;timeout&#34;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="k">class</span> <span class="nc">DetailedResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="s2">&#34;&#34;&#34;包含狀態的詳細結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="n">path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="n">status</span><span class="p">:</span> <span class="n">ValidationStatus</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="n">result</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="c1"># ===== 驗證器 =====</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器（簡化版）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="o">.</span><span class="n">cwd</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 檔案不存在: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取 Hook 檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_check_naming</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_check_imports</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_naming</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">VALID_NAME_PATTERNS</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案名稱不符合規範: </span><span class="si">{</span><span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查導入規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_IO_PATTERNS</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;未導入 hook_io 模組&#34;</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="c1"># ===== 並行驗證 =====</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_sync</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="s2">    同步版本（基準對照）
</span></span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">
</span></span><span class="line"><span class="ln">155</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">
</span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_map</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">4</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">162</span><span class="cl"><span class="s2">    使用 map() 的並行版本
</span></span></span><span class="line"><span class="ln">163</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">            <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">            <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">
</span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_async</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="n">progress_callback</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="kc">None</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="s2">    使用 submit() + as_completed() 的並行版本
</span></span></span><span class="line"><span class="ln">181</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">183</span><span class="cl"><span class="s2">        hook_files: Hook 檔案列表
</span></span></span><span class="line"><span class="ln">184</span><span class="cl"><span class="s2">        max_workers: 最大執行緒數
</span></span></span><span class="line"><span class="ln">185</span><span class="cl"><span class="s2">        progress_callback: 進度回調 (completed, total, filename)
</span></span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">187</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">188</span><span class="cl"><span class="s2">        驗證結果列表
</span></span></span><span class="line"><span class="ln">189</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="c1"># 依完成順序處理</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="k">for</span> <span class="n">completed</span><span class="p">,</span> <span class="n">future</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">            <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">            <span class="n">start</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">
</span></span><span class="line"><span class="ln">208</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">
</span></span><span class="line"><span class="ln">220</span><span class="cl">            <span class="k">if</span> <span class="n">progress_callback</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">                <span class="n">progress_callback</span><span class="p">(</span><span class="n">completed</span><span class="p">,</span> <span class="n">total</span><span class="p">,</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">
</span></span><span class="line"><span class="ln">223</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">
</span></span><span class="line"><span class="ln">225</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_error_handling</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">    <span class="n">timeout_per_file</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">5.0</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">231</span><span class="cl"><span class="s2">    帶完善錯誤處理的並行驗證
</span></span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">    <span class="n">detailed_results</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">
</span></span><span class="line"><span class="ln">242</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">
</span></span><span class="line"><span class="ln">245</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">timeout_per_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">                    <span class="n">result</span><span class="o">=</span><span class="n">result</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">            <span class="k">except</span> <span class="n">FuturesTimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證超時 (</span><span class="si">{</span><span class="n">timeout_per_file</span><span class="si">}</span><span class="s2">s)&#34;</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">FAILED</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">
</span></span><span class="line"><span class="ln">265</span><span class="cl">    <span class="k">return</span> <span class="n">detailed_results</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">
</span></span><span class="line"><span class="ln">267</span><span class="cl"><span class="c1"># ===== 進度顯示 =====</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">
</span></span><span class="line"><span class="ln">269</span><span class="cl"><span class="k">def</span> <span class="nf">print_progress</span><span class="p">(</span><span class="n">completed</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">    <span class="s2">&#34;&#34;&#34;進度條顯示&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">    <span class="n">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="n">bar_length</span> <span class="o">=</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">    <span class="n">filled</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="n">bar</span> <span class="o">=</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="n">filled</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">
</span></span><span class="line"><span class="ln">276</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">bar</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">percentage</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">%) - </span><span class="si">{</span><span class="n">filename</span><span class="si">:</span><span class="s2">&lt;30</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">
</span></span><span class="line"><span class="ln">281</span><span class="cl">    <span class="k">if</span> <span class="n">completed</span> <span class="o">==</span> <span class="n">total</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">
</span></span><span class="line"><span class="ln">284</span><span class="cl"><span class="c1"># ===== 效能測試 =====</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">
</span></span><span class="line"><span class="ln">286</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">288</span><span class="cl"><span class="s2">    比較不同策略的執行時間
</span></span></span><span class="line"><span class="ln">289</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">
</span></span><span class="line"><span class="ln">292</span><span class="cl">    <span class="c1"># 同步版本</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">        <span class="n">validate_all_hooks_sync</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;sync&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="c1"># map() 版本</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">303</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">        <span class="n">validate_all_hooks_map</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;map&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">
</span></span><span class="line"><span class="ln">308</span><span class="cl">    <span class="c1"># as_completed() 版本</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">310</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">        <span class="n">validate_all_hooks_async</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;as_completed&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">
</span></span><span class="line"><span class="ln">318</span><span class="cl"><span class="c1"># ===== 示範 =====</span>
</span></span><span class="line"><span class="ln">319</span><span class="cl">
</span></span><span class="line"><span class="ln">320</span><span class="cl"><span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範並行 Hook 驗證&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 並行 Hook 驗證示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">
</span></span><span class="line"><span class="ln">324</span><span class="cl">    <span class="c1"># 建立測試用的 Hook 檔案</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">    <span class="n">test_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/tmp/test_hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">    <span class="n">test_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">
</span></span><span class="line"><span class="ln">328</span><span class="cl">    <span class="n">hook_files</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">20</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">        <span class="n">hook_file</span> <span class="o">=</span> <span class="n">test_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;hook-</span><span class="si">{</span><span class="n">i</span><span class="si">:</span><span class="s2">02d</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl">        <span class="n">hook_file</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;&#39;&#39;#!/usr/bin/env python3
</span></span></span><span class="line"><span class="ln">332</span><span class="cl"><span class="s1">&#34;&#34;&#34;Test hook </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s1">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">333</span><span class="cl"><span class="s1">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln">334</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln">335</span><span class="cl"><span class="s1">def main():
</span></span></span><span class="line"><span class="ln">336</span><span class="cl"><span class="s1">    data = read_hook_input()
</span></span></span><span class="line"><span class="ln">337</span><span class="cl"><span class="s1">    write_hook_output(</span><span class="se">{{</span><span class="s1">&#34;status&#34;: &#34;ok&#34;</span><span class="se">}}</span><span class="s1">)
</span></span></span><span class="line"><span class="ln">338</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln">339</span><span class="cl"><span class="s1">if __name__ == &#34;__main__&#34;:
</span></span></span><span class="line"><span class="ln">340</span><span class="cl"><span class="s1">    main()
</span></span></span><span class="line"><span class="ln">341</span><span class="cl"><span class="s1">&#39;&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">        <span class="n">hook_files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;測試檔案數: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">
</span></span><span class="line"><span class="ln">346</span><span class="cl">    <span class="c1"># 效能比較</span>
</span></span><span class="line"><span class="ln">347</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. 效能比較:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">    <span class="k">for</span> <span class="n">strategy</span><span class="p">,</span> <span class="n">elapsed</span> <span class="ow">in</span> <span class="n">times</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">350</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   </span><span class="si">{</span><span class="n">strategy</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">
</span></span><span class="line"><span class="ln">352</span><span class="cl">    <span class="n">speedup</span> <span class="o">=</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;sync&#34;</span><span class="p">]</span> <span class="o">/</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;as_completed&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">353</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   加速比: </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">354</span><span class="cl">
</span></span><span class="line"><span class="ln">355</span><span class="cl">    <span class="c1"># 帶進度的驗證</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;2. 帶進度報告的驗證:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">357</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">validate_all_hooks_async</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">        <span class="n">hook_files</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">        <span class="n">progress_callback</span><span class="o">=</span><span class="n">print_progress</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">
</span></span><span class="line"><span class="ln">362</span><span class="cl">    <span class="n">compliant</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">   合規: </span><span class="si">{</span><span class="n">compliant</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">364</span><span class="cl">
</span></span><span class="line"><span class="ln">365</span><span class="cl">    <span class="c1"># 清理測試檔案</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">    <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">367</span><span class="cl">        <span class="n">f</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">    <span class="n">test_dir</span><span class="o">.</span><span class="n">rmdir</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">369</span><span class="cl">
</span></span><span class="line"><span class="ln">370</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">    <span class="n">demo</span><span class="p">()</span></span></span></code></pre></div><h2 id="效能測量">效能測量</h2>
<h3 id="測試環境">測試環境</h3>
<ul>
<li>Python 3.11</li>
<li>20 個 Hook 檔案</li>
<li>每個驗證包含：檔案讀取、正則匹配、路徑檢查</li>
</ul>
<h3 id="測試結果">測試結果</h3>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>執行時間</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同步 (基準)</td>
          <td>0.85s</td>
          <td>1.0x</td>
      </tr>
      <tr>
          <td>map()</td>
          <td>0.25s</td>
          <td>3.4x</td>
      </tr>
      <tr>
          <td>as_completed()</td>
          <td>0.26s</td>
          <td>3.3x</td>
      </tr>
      <tr>
          <td>as_completed() + 進度</td>
          <td>0.27s</td>
          <td>3.1x</td>
      </tr>
  </tbody>
</table>
<p><strong>觀察</strong>：</p>
<ol>
<li><code>map()</code> 和 <code>as_completed()</code> 效能相近</li>
<li>進度報告的額外開銷約 3-5%</li>
<li>實際加速比接近 <code>min(hook_count, max_workers)</code></li>
</ol>
<h3 id="不同檔案數量的效能">不同檔案數量的效能</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Hook 數量    同步      並行(4)    加速比
</span></span><span class="line"><span class="ln">2</span><span class="cl">-----------------------------------------
</span></span><span class="line"><span class="ln">3</span><span class="cl">5           0.21s     0.08s      2.6x
</span></span><span class="line"><span class="ln">4</span><span class="cl">10          0.42s     0.14s      3.0x
</span></span><span class="line"><span class="ln">5</span><span class="cl">20          0.85s     0.26s      3.3x
</span></span><span class="line"><span class="ln">6</span><span class="cl">50          2.10s     0.58s      3.6x
</span></span><span class="line"><span class="ln">7</span><span class="cl">100         4.25s     1.12s      3.8x</span></span></code></pre></div><p>加速比隨檔案數量增加而提升，趨近於 <code>max_workers</code> 數量。</p>
<h2 id="設計權衡">設計權衡</h2>
<h3 id="map-vs-as_completed-選擇指南">map() vs as_completed() 選擇指南</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">需要並行處理多個獨立任務？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 是 → 需要即時進度報告？
</span></span><span class="line"><span class="ln">3</span><span class="cl">│        ├── 是 → 使用 submit() + as_completed()
</span></span><span class="line"><span class="ln">4</span><span class="cl">│        └── 否 → 需要細緻的錯誤處理？
</span></span><span class="line"><span class="ln">5</span><span class="cl">│                 ├── 是 → 使用 submit() + as_completed()
</span></span><span class="line"><span class="ln">6</span><span class="cl">│                 └── 否 → 使用 map()（更簡潔）
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── 否 → 直接循序執行</span></span></code></pre></div><h3 id="比較表">比較表</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>map()</th>
          <th>submit() + as_completed()</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>程式碼複雜度</td>
          <td>低</td>
          <td>中</td>
      </tr>
      <tr>
          <td>結果順序</td>
          <td>保持輸入順序</td>
          <td>按完成順序</td>
      </tr>
      <tr>
          <td>進度報告</td>
          <td>不支援</td>
          <td>支援</td>
      </tr>
      <tr>
          <td>異常處理</td>
          <td>第一個異常就停止</td>
          <td>可逐一處理</td>
      </tr>
      <tr>
          <td>單一任務超時</td>
          <td>不支援</td>
          <td>支援</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>批次處理，不需即時回饋</td>
          <td>需要進度報告或細緻錯誤處理</td>
      </tr>
  </tbody>
</table>
<h3 id="進度報告的開銷">進度報告的開銷</h3>
<table>
  <thead>
      <tr>
          <th>進度報告方式</th>
          <th>額外開銷</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>無</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>簡單計數器</td>
          <td>~1%</td>
      </tr>
      <tr>
          <td>進度條（無 flush）</td>
          <td>~2%</td>
      </tr>
      <tr>
          <td>進度條（每次 flush）</td>
          <td>~5%</td>
      </tr>
      <tr>
          <td>詳細進度（含時間估算）</td>
          <td>~8%</td>
      </tr>
  </tbody>
</table>
<p>對於大量任務（&gt;100），建議每 N 個任務更新一次進度，而非每個任務都更新。</p>
<h2 id="練習">練習</h2>
<h3 id="練習-1加入跳過已驗證功能">練習 1：加入「跳過已驗證」功能</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_cache</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">cache</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ValidationResult</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    只驗證快取中沒有的檔案
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 檢查 cache 中是否已有結果
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 只對新檔案提交任務
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 合併快取結果和新結果
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="練習-2實作取消機制">練習 2：實作取消機制</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_cancel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">should_cancel</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="nb">bool</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    支援取消的並行驗證
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    當 should_cancel() 返回 True 時，取消所有未完成的任務。
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 使用 future.cancel() 取消未開始的任務
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 已開始的任務無法取消，需等待完成
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 返回已完成的結果
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="練習-3實作優先順序">練習 3：實作優先順序</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_priority</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">priority_fn</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Path</span><span class="p">],</span> <span class="nb">int</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    按優先順序驗證
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    高優先順序的檔案先被驗證。
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 按優先順序排序後提交
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 但 as_completed 仍按完成順序返回
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 考慮使用 PriorityQueue 控制提交順序
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題實作可暫停恢復的驗證">挑戰題：實作可暫停/恢復的驗證</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">PausableValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    可暫停和恢復的驗證器
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用方式：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        validator = PausableValidator(hook_files)
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        validator.start()
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        # ...
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        validator.pause()  # 暫停，已提交的任務會完成
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        # ...
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        validator.resume()  # 恢復
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        results = validator.get_results()
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    - 使用 threading.Event 控制暫停
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    - 追蹤已完成和未開始的任務
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    - 恢復時只提交剩餘任務
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_hook_files</span> <span class="o">=</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_paused</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">def</span> <span class="nf">start</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">def</span> <span class="nf">pause</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">def</span> <span class="nf">resume</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">def</span> <span class="nf">get_results</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html">concurrent.futures 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed">as_completed 與 wait 的差異</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html#future-objects">Future 物件的方法</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">並行檔案檢查</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯</a></em></p>
]]></content:encoded></item><item><title>案例：並行 I/O 操作</title><link>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/parallel-io/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/parallel-io/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/git_utils.py&lt;/code> 的實際程式碼，展示如何用 &lt;code>asyncio.gather&lt;/code> 和 &lt;code>TaskGroup&lt;/code> 實現高效的並行 I/O 操作。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">1.1 非同步 Subprocess&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">1.2 協程與 Task 管理&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>git_utils.py&lt;/code> 的 &lt;code>get_worktree_list()&lt;/code> 取得 worktree 列表後，如果要檢查每個 worktree 的狀態，需要逐一呼叫：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_worktree_list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_all_worktrees_sync&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> 同步版本：依序檢查每個 worktree 的狀態
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict[str, str]: {路徑: 狀態} 映射
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">wt&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Every call blocks until completion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_all_branches_sync&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2"> 同步版本：依序取得每個 worktree 的分支名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict[str, str]: {路徑: 分支名} 映射
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">wt&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Each command waits for the previous one&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-current&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;unknown&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：循序執行，容易理解和除錯&lt;/li>
&lt;li>&lt;strong>錯誤處理明確&lt;/strong>：每個操作的結果立即可用&lt;/li>
&lt;li>&lt;strong>資源友善&lt;/strong>：一次只執行一個進程&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當 worktree 數量增加時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>執行時間線性增長&lt;/strong>：10 個 worktree，每個 0.2 秒 = 2 秒&lt;/li>
&lt;li>&lt;strong>無法利用 I/O 等待時間&lt;/strong>：等待一個命令完成時，CPU 閒置&lt;/li>
&lt;li>&lt;strong>使用者體驗差&lt;/strong>：大量 worktree 時響應緩慢&lt;/li>
&lt;/ul>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">benchmark_sync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">float&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測量同步版本的執行時間&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Simulate I/O wait (actual git command)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 10 worktrees, each taking 0.2s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># Total: 10 * 0.2 = 2.0 seconds&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>並行執行&lt;/strong>多個獨立的 I/O 操作&lt;/li>
&lt;li>&lt;strong>正確處理&lt;/strong>部分失敗的情況&lt;/li>
&lt;li>&lt;strong>支援取消&lt;/strong>和超時機制&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1使用-asynciogather">步驟 1：使用 asyncio.gather&lt;/h4>
&lt;p>&lt;code>asyncio.gather&lt;/code> 是並行執行多個協程最直接的方式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">args&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">float&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">10.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 非同步執行 git 命令（詳見上一章）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_subprocess_exec&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">cwd&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">stdout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">stderr&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">stdout&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stderr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wait_for&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">communicate&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">timeout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TimeoutError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kill&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Command timed out after &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">timeout&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">returncode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stderr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;git command not found&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_all_worktrees_basic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 asyncio.gather 並行檢查所有 worktree
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2"> worktrees: worktree 路徑列表
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict[str, str]: {路徑: 狀態} 映射
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_one&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Check a single worktree and return (path, status)&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Create tasks for all worktrees&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">check_one&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Execute all tasks in parallel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Convert list of tuples to dict&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">results&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl">&lt;span class="c1"># Usage example&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">demo_basic&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;/path/to/repo1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;/path/to/repo2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;/path/to/repo3&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="c1"># All three checks run in parallel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl"> &lt;span class="c1"># If each takes 0.2s, total time is ~0.2s, not 0.6s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl"> &lt;span class="n">statuses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">check_all_worktrees_basic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">74&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">75&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">statuses&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">76&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39;clean&amp;#39;&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">status&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>重點說明&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/git_utils.py</code> 的實際程式碼，展示如何用 <code>asyncio.gather</code> 和 <code>TaskGroup</code> 實現高效的並行 I/O 操作。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">1.1 非同步 Subprocess</a></li>
<li><a href="/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">1.2 協程與 Task 管理</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>git_utils.py</code> 的 <code>get_worktree_list()</code> 取得 worktree 列表後，如果要檢查每個 worktree 的狀態，需要逐一呼叫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">run_git_command</span><span class="p">,</span> <span class="n">get_worktree_list</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">check_all_worktrees_sync</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    同步版本：依序檢查每個 worktree 的狀態
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="n">get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># Every call blocks until completion</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">def</span> <span class="nf">get_all_branches_sync</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    同步版本：依序取得每個 worktree 的分支名稱
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 分支名} 映射
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="n">get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="c1"># Each command waits for the previous one</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>簡單直覺</strong>：循序執行，容易理解和除錯</li>
<li><strong>錯誤處理明確</strong>：每個操作的結果立即可用</li>
<li><strong>資源友善</strong>：一次只執行一個進程</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當 worktree 數量增加時：</p>
<ul>
<li><strong>執行時間線性增長</strong>：10 個 worktree，每個 0.2 秒 = 2 秒</li>
<li><strong>無法利用 I/O 等待時間</strong>：等待一個命令完成時，CPU 閒置</li>
<li><strong>使用者體驗差</strong>：大量 worktree 時響應緩慢</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_sync</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量同步版本的執行時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># Simulate I/O wait (actual git command)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 10 worktrees, each taking 0.2s</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># Total: 10 * 0.2 = 2.0 seconds</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>並行執行</strong>多個獨立的 I/O 操作</li>
<li><strong>正確處理</strong>部分失敗的情況</li>
<li><strong>支援取消</strong>和超時機制</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1使用-asynciogather">步驟 1：使用 asyncio.gather</h4>
<p><code>asyncio.gather</code> 是並行執行多個協程最直接的方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">10.0</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    非同步執行 git 命令（詳見上一章）
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">process</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_subprocess_exec</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">stdout</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">stderr</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">wait_for</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">                <span class="n">process</span><span class="o">.</span><span class="n">communicate</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">                <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">except</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">process</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="k">await</span> <span class="n">process</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Command timed out after </span><span class="si">{</span><span class="n">timeout</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="n">process</span><span class="o">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="n">stdout</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="n">stderr</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;git command not found&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_worktrees_basic</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    使用 asyncio.gather 並行檢查所有 worktree
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check a single worktree and return (path, status)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="c1"># Create tasks for all worktrees</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="c1"># Execute all tasks in parallel</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="c1"># Convert list of tuples to dict</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="c1"># Usage example</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo_basic</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;/path/to/repo1&#34;</span><span class="p">,</span> <span class="s2">&#34;/path/to/repo2&#34;</span><span class="p">,</span> <span class="s2">&#34;/path/to/repo3&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">
</span></span><span class="line"><span class="ln">71</span><span class="cl">    <span class="c1"># All three checks run in parallel</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">    <span class="c1"># If each takes 0.2s, total time is ~0.2s, not 0.6s</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">    <span class="n">statuses</span> <span class="o">=</span> <span class="k">await</span> <span class="n">check_all_worktrees_basic</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">
</span></span><span class="line"><span class="ln">75</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">statuses</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="s1">&#39;clean&#39;</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">status</span> <span class="k">else</span> <span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>重點說明</strong>：</p>
<ul>
<li><code>asyncio.gather(*tasks)</code> 同時啟動所有協程</li>
<li>等待所有協程完成後，返回結果列表</li>
<li>結果順序與輸入任務順序一致</li>
</ul>
<h4 id="步驟-2處理錯誤return_exceptions">步驟 2：處理錯誤（return_exceptions）</h4>
<p>預設情況下，<code>gather</code> 在遇到第一個異常時會傳播該異常。使用 <code>return_exceptions=True</code> 可以收集所有結果，包括異常：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_worktrees_safe</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span> <span class="o">|</span> <span class="ne">Exception</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    安全版本：使用 return_exceptions 處理部分失敗
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    即使某些 worktree 檢查失敗，仍然返回其他的結果。
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        dict: {路徑: 狀態或例外} 映射
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check with potential exception&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">timeout</span><span class="o">=</span><span class="mf">5.0</span>  <span class="c1"># Shorter timeout</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="c1"># Raise exception for failed commands</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Git command failed: </span><span class="si">{</span><span class="n">output</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="c1"># return_exceptions=True: exceptions become results, not propagated</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="c1"># Process results, handling both successes and exceptions</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">output</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">result</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">worktrees</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="n">output</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;error: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="c1"># result is (path, status) tuple</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="n">_</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">output</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">status</span> <span class="k">if</span> <span class="n">status</span> <span class="k">else</span> <span class="s2">&#34;clean&#34;</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">return</span> <span class="n">output</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo_error_handling</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範錯誤處理&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="s2">&#34;/valid/repo1&#34;</span><span class="p">,</span>      <span class="c1"># Works</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="s2">&#34;/invalid/path&#34;</span><span class="p">,</span>     <span class="c1"># Fails</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="s2">&#34;/valid/repo2&#34;</span><span class="p">,</span>      <span class="c1"># Works</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">check_all_worktrees_safe</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">results</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">if</span> <span class="n">status</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;error:&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[FAILED] </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[OK] </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">
</span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="c1"># [OK] /valid/repo1: clean</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="c1"># [FAILED] /invalid/path: error: Git command failed: ...</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="c1"># [OK] /valid/repo2: M file.txt</span></span></span></code></pre></div><p><strong><code>return_exceptions</code> 行為對比</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">compare_exception_handling</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">might_fail</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s2"> failed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">return</span> <span class="n">n</span> <span class="o">*</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">might_fail</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># Without return_exceptions (default)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>  <span class="c1"># Raises ValueError</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Caught: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># Only see first error, others lost</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># With return_exceptions=True</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># results: [0, 10, ValueError(&#39;Task 2 failed&#39;), 30, 40]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">result</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">results</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">: Failed - </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">: Success - </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-3使用-taskgrouppython-311">步驟 3：使用 TaskGroup（Python 3.11+）</h4>
<p>Python 3.11 引入的 <code>TaskGroup</code> 提供更好的結構化並行控制：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_worktrees_taskgroup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    使用 TaskGroup 並行檢查所有 worktree
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    TaskGroup 特性：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 任一任務失敗時，自動取消其他任務
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 明確的作用域（context manager）
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 異常聚合（ExceptionGroup）
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_and_store</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check worktree and store result in shared dict&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">check_and_store</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="c1"># All tasks complete when exiting the context</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo_taskgroup</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範 TaskGroup 的基本用法&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;/repo1&#34;</span><span class="p">,</span> <span class="s2">&#34;/repo2&#34;</span><span class="p">,</span> <span class="s2">&#34;/repo3&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">check_all_worktrees_taskgroup</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">results</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="c1"># Python 3.11+ except* syntax for ExceptionGroup</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">for</span> <span class="n">exc</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task failed: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>TaskGroup 的錯誤處理模式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">taskgroup_error_demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範 TaskGroup 的異常處理&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">task_might_fail</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">should_fail</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">if</span> <span class="n">should_fail</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> failed!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> succeeded&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task_might_fail</span><span class="p">(</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="kc">False</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task_might_fail</span><span class="p">(</span><span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="kc">True</span><span class="p">))</span>   <span class="c1"># Will fail</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task_might_fail</span><span class="p">(</span><span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="kc">False</span><span class="p">))</span>  <span class="c1"># Gets cancelled</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Caught </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> errors:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">for</span> <span class="n">exc</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  - </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># When B fails:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"># 1. TaskGroup cancels C (even though it would succeed)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 2. Waits for all tasks to finish</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># 3. Raises ExceptionGroup with the ValueError</span></span></span></code></pre></div><h4 id="步驟-4並行與循序的混合模式">步驟 4：並行與循序的混合模式</h4>
<p>實際應用中，常需要混合並行和循序操作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_worktrees_batched</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">    <span class="n">batch_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">    分批並行處理：控制同時執行的任務數量
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    適用場景：
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">    - 避免同時開啟過多進程
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    - 防止 API rate limiting
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    - 控制資源使用
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="s2">        batch_size: 每批並行數量
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="c1"># Process in batches</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">worktrees</span><span class="p">),</span> <span class="n">batch_size</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="n">batch</span> <span class="o">=</span> <span class="n">worktrees</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span> <span class="o">+</span> <span class="n">batch_size</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">            <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">                <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">                <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">            <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="c1"># Process batch in parallel</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="n">batch_results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">            <span class="o">*</span><span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">batch</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="c1"># Collect results</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="nb">dict</span><span class="p">(</span><span class="n">batch_results</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_worktrees_semaphore</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="n">max_concurrent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="s2">    使用 Semaphore 限制並行數量
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">    比分批更靈活：任務完成後立即啟動新任務，
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">    而不是等整批完成。
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">        max_concurrent: 最大同時執行數
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="n">semaphore</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="n">max_concurrent</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_with_limit</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check worktree with concurrency limit&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">semaphore</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="c1"># At most max_concurrent tasks run this block</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">            <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">            <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="c1"># Launch all tasks (they&#39;ll wait at semaphore if needed)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_with_limit</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">pipeline_example</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">    示範流水線模式：前一步的輸出是後一步的輸入
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="c1"># Step 1: Get worktree list (single operation)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="c1"># Step 2: Check status in parallel</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="n">statuses</span> <span class="o">=</span> <span class="k">await</span> <span class="n">check_all_worktrees_basic</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">    <span class="c1"># Step 3: For dirty repos, get detailed diff (parallel)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="n">dirty_paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span><span class="p">,</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">statuses</span><span class="o">.</span><span class="n">items</span><span class="p">()</span> <span class="k">if</span> <span class="n">s</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_diff</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">diff</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;diff&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">diff</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="n">diffs</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="o">*</span><span class="p">[</span><span class="n">get_diff</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">dirty_paths</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;statuses&#34;</span><span class="p">:</span> <span class="n">statuses</span><span class="p">,</span> <span class="s2">&#34;diffs&#34;</span><span class="p">:</span> <span class="n">diffs</span><span class="p">}</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">並行 I/O 操作工具 - 完整範例
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 asyncio.gather 和 TaskGroup 實現高效的並行 Git 操作。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="c1"># ===== Core async function =====</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">10.0</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="s2">    非同步執行 git 命令
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">        args: git 命令參數列表
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">        cwd: 執行目錄
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">        timeout: 超時時間（秒）
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">        (是否成功, 輸出或錯誤訊息)
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="n">process</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_subprocess_exec</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">            <span class="n">stdout</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">            <span class="n">stderr</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">            <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">wait_for</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">                <span class="n">process</span><span class="o">.</span><span class="n">communicate</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">                <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="k">except</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">            <span class="n">process</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="k">await</span> <span class="n">process</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Command timed out after </span><span class="si">{</span><span class="n">timeout</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="k">if</span> <span class="n">process</span><span class="o">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="n">stdout</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="n">stderr</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;git command not found&#34;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取 worktree 列表&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;worktree&#34;</span><span class="p">,</span> <span class="s2">&#34;list&#34;</span><span class="p">,</span> <span class="s2">&#34;--porcelain&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="n">current_worktree</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">output</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;worktree &#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">            <span class="k">if</span> <span class="n">current_worktree</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                <span class="n">worktrees</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_worktree</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">            <span class="n">current_worktree</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;path&#34;</span><span class="p">:</span> <span class="n">line</span><span class="p">[</span><span class="mi">9</span><span class="p">:]}</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="k">elif</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;branch &#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">            <span class="n">branch_ref</span> <span class="o">=</span> <span class="n">line</span><span class="p">[</span><span class="mi">7</span><span class="p">:]</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="k">if</span> <span class="n">branch_ref</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;refs/heads/&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">                <span class="n">branch_ref</span> <span class="o">=</span> <span class="n">branch_ref</span><span class="p">[</span><span class="mi">11</span><span class="p">:]</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="n">current_worktree</span><span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">branch_ref</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="k">elif</span> <span class="n">line</span> <span class="o">==</span> <span class="s2">&#34;detached&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">            <span class="n">current_worktree</span><span class="p">[</span><span class="s2">&#34;detached&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="k">if</span> <span class="n">current_worktree</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="n">worktrees</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_worktree</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="k">return</span> <span class="n">worktrees</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="c1"># ===== Parallel strategies =====</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">parallel_with_gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="n">return_exceptions</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="s2">    Strategy 1: asyncio.gather
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">        return_exceptions: 是否將異常作為結果返回
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">        dict[str, str]: 檢查結果
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="n">return_exceptions</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="n">output</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">result</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">worktrees</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">            <span class="n">output</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;exception: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="n">_</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="n">output</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">status</span> <span class="k">if</span> <span class="n">status</span> <span class="k">else</span> <span class="s2">&#34;clean&#34;</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="k">return</span> <span class="n">output</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">parallel_with_taskgroup</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="s2">    Strategy 2: TaskGroup (Python 3.11+)
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="s2">    One task fails -&gt; all cancelled
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_and_store</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">check_and_store</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">parallel_with_semaphore</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="n">max_concurrent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">149</span><span class="cl"><span class="s2">    Strategy 3: Semaphore for rate limiting
</span></span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">    <span class="n">semaphore</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="n">max_concurrent</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">
</span></span><span class="line"><span class="ln">153</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_with_limit</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">semaphore</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">            <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">                <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">                <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">            <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_with_limit</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl"><span class="c1"># ===== Practical helpers =====</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">
</span></span><span class="line"><span class="ln">168</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_worktree_status_report</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">170</span><span class="cl"><span class="s2">    生成完整的 worktree 狀態報告
</span></span></span><span class="line"><span class="ln">171</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">172</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="s2">        dict: 包含狀態、分支、變更的完整報告
</span></span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="c1"># Step 1: Get worktree list</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">paths</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;No worktrees found&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">
</span></span><span class="line"><span class="ln">182</span><span class="cl">    <span class="c1"># Step 2: Parallel operations</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_status</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_branch</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">
</span></span><span class="line"><span class="ln">197</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_last_commit</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;log&#34;</span><span class="p">,</span> <span class="s2">&#34;-1&#34;</span><span class="p">,</span> <span class="s2">&#34;--format=</span><span class="si">%s</span><span class="s2">&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">
</span></span><span class="line"><span class="ln">204</span><span class="cl">    <span class="c1"># Execute all queries in parallel</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">    <span class="n">status_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">get_status</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">paths</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">    <span class="n">branch_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">get_branch</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">paths</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">    <span class="n">commit_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">get_last_commit</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">paths</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">
</span></span><span class="line"><span class="ln">209</span><span class="cl">    <span class="n">statuses</span><span class="p">,</span> <span class="n">branches</span><span class="p">,</span> <span class="n">commits</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="n">status_task</span><span class="p">,</span> <span class="n">branch_task</span><span class="p">,</span> <span class="n">commit_task</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">
</span></span><span class="line"><span class="ln">213</span><span class="cl">    <span class="c1"># Combine results</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">    <span class="n">report</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">        <span class="n">status_dict</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">statuses</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">        <span class="n">branch_dict</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">branches</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">        <span class="n">commit_dict</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">commits</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="n">report</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">            <span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;unknown&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="n">status_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;error&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">            <span class="s2">&#34;is_clean&#34;</span><span class="p">:</span> <span class="ow">not</span> <span class="n">status_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;error&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">            <span class="s2">&#34;last_commit&#34;</span><span class="p">:</span> <span class="n">commit_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;unknown&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">
</span></span><span class="line"><span class="ln">228</span><span class="cl">    <span class="k">return</span> <span class="n">report</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">
</span></span><span class="line"><span class="ln">230</span><span class="cl"><span class="c1"># ===== Benchmark =====</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">
</span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">benchmark_strategies</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">234</span><span class="cl"><span class="s2">    比較不同策略的執行時間
</span></span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">    <span class="c1"># Strategy 1: Sequential (baseline)</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">    <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">        <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;sequential&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">
</span></span><span class="line"><span class="ln">244</span><span class="cl">    <span class="c1"># Strategy 2: gather</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">    <span class="k">await</span> <span class="n">parallel_with_gather</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;gather&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">
</span></span><span class="line"><span class="ln">249</span><span class="cl">    <span class="c1"># Strategy 3: TaskGroup</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">    <span class="k">await</span> <span class="n">parallel_with_taskgroup</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;taskgroup&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">
</span></span><span class="line"><span class="ln">254</span><span class="cl">    <span class="c1"># Strategy 4: Semaphore</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">    <span class="k">await</span> <span class="n">parallel_with_semaphore</span><span class="p">(</span><span class="n">worktrees</span><span class="p">,</span> <span class="n">max_concurrent</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;semaphore(3)&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">
</span></span><span class="line"><span class="ln">259</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">
</span></span><span class="line"><span class="ln">261</span><span class="cl"><span class="c1"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">
</span></span><span class="line"><span class="ln">263</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範並行 I/O 操作&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 並行 I/O 操作示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">
</span></span><span class="line"><span class="ln">267</span><span class="cl">    <span class="c1"># Get worktrees</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. 獲取 worktree 列表:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">    <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">        <span class="n">branch</span> <span class="o">=</span> <span class="n">wt</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;detached&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   - </span><span class="si">{</span><span class="n">branch</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">wt</span><span class="p">[</span><span class="s1">&#39;path&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">
</span></span><span class="line"><span class="ln">277</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">        <span class="c1"># Benchmark</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;2. 效能比較:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">        <span class="n">times</span> <span class="o">=</span> <span class="k">await</span> <span class="n">benchmark_strategies</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">        <span class="k">for</span> <span class="n">strategy</span><span class="p">,</span> <span class="n">elapsed</span> <span class="ow">in</span> <span class="n">times</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   </span><span class="si">{</span><span class="n">strategy</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="n">speedup</span> <span class="o">=</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;sequential&#34;</span><span class="p">]</span> <span class="o">/</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;gather&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   加速比: </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">
</span></span><span class="line"><span class="ln">288</span><span class="cl">        <span class="c1"># Full report</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;3. 完整狀態報告:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">        <span class="n">report</span> <span class="o">=</span> <span class="k">await</span> <span class="n">get_worktree_status_report</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">        <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="n">report</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">            <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;clean&#34;</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">&#34;is_clean&#34;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;dirty&#34;</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   [</span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;branch&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;       狀態: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;       最新提交: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;last_commit&#39;</span><span class="p">][:</span><span class="mi">50</span><span class="p">]</span><span class="si">}</span><span class="s2">...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">
</span></span><span class="line"><span class="ln">297</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">demo</span><span class="p">())</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="基本使用">基本使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># Get all worktree paths</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># Check all in parallel</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">statuses</span> <span class="o">=</span> <span class="k">await</span> <span class="n">parallel_with_gather</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">statuses</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span> <span class="ow">or</span> <span class="s1">&#39;clean&#39;</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><h4 id="處理大量-worktree">處理大量 worktree</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_many_worktrees</span><span class="p">(</span><span class="n">paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理大量 worktree 時使用 semaphore 限制並行數&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># Limit to 10 concurrent git processes</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">parallel_with_semaphore</span><span class="p">(</span><span class="n">paths</span><span class="p">,</span> <span class="n">max_concurrent</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">clean_count</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">results</span><span class="o">.</span><span class="n">values</span><span class="p">()</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">s</span> <span class="ow">or</span> <span class="n">s</span> <span class="o">==</span> <span class="s2">&#34;clean&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">dirty_count</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span> <span class="o">-</span> <span class="n">clean_count</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Clean: </span><span class="si">{</span><span class="n">clean_count</span><span class="si">}</span><span class="s2">, Dirty: </span><span class="si">{</span><span class="n">dirty_count</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h4 id="與錯誤重試結合">與錯誤重試結合</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_with_retry</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">max_retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;帶重試的檢查&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">for</span> <span class="n">attempt</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_retries</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">timeout</span><span class="o">=</span><span class="mf">5.0</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">if</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">if</span> <span class="n">attempt</span> <span class="o">&lt;</span> <span class="n">max_retries</span> <span class="o">-</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span> <span class="o">*</span> <span class="p">(</span><span class="n">attempt</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span>  <span class="c1"># Backoff</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="s2">&#34;error: max retries exceeded&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_with_retry</span><span class="p">(</span><span class="n">paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;所有檢查都帶重試機制&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_with_retry</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">paths</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>asyncio.gather</th>
          <th>TaskGroup</th>
          <th>Semaphore</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Python 版本</td>
          <td>3.4+</td>
          <td>3.11+</td>
          <td>3.4+</td>
      </tr>
      <tr>
          <td>錯誤處理</td>
          <td><code>return_exceptions</code></td>
          <td>自動取消其他任務</td>
          <td>同 gather</td>
      </tr>
      <tr>
          <td>取消行為</td>
          <td>需手動處理</td>
          <td>結構化取消</td>
          <td>需手動處理</td>
      </tr>
      <tr>
          <td>程式碼可讀性</td>
          <td>中等</td>
          <td>較高</td>
          <td>中等</td>
      </tr>
      <tr>
          <td>並行控制</td>
          <td>無內建限制</td>
          <td>無內建限制</td>
          <td>可限制數量</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>一般並行</td>
          <td>全有或全無</td>
          <td>需要限流</td>
      </tr>
  </tbody>
</table>
<h3 id="選擇指南">選擇指南</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">需要並行執行多個獨立 I/O 操作？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 是 → 是否需要「一個失敗全部取消」？
</span></span><span class="line"><span class="ln">3</span><span class="cl">│        ├── 是 → 使用 TaskGroup
</span></span><span class="line"><span class="ln">4</span><span class="cl">│        └── 否 → 是否需要限制並行數量？
</span></span><span class="line"><span class="ln">5</span><span class="cl">│                 ├── 是 → 使用 Semaphore + gather
</span></span><span class="line"><span class="ln">6</span><span class="cl">│                 └── 否 → 使用 gather（可選 return_exceptions）
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── 否 → 直接使用 await</span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>多個獨立的 I/O 操作（HTTP 請求、檔案讀取、資料庫查詢）</li>
<li>需要等待所有操作完成</li>
<li>操作之間沒有依賴關係</li>
<li>單一操作耗時較長（&gt; 10ms）</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>CPU 密集計算（應用 multiprocessing）</li>
<li>操作之間有依賴關係（應用循序執行或流水線）</li>
<li>單一操作極快（overhead 可能大於收益）</li>
<li>外部服務有嚴格的 rate limit（需要更精細的控制）</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="練習-1用-gather-同時讀取多個設定檔">練習 1：用 gather 同時讀取多個設定檔</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">read_configs</span><span class="p">(</span><span class="n">paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s2">    並行讀取多個設定檔
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="s2">    提示：使用 aiofiles 或 asyncio.to_thread
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-2實作-worktree-狀態快取">練習 2：實作 worktree 狀態快取</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">WorktreeCache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    快取 worktree 狀態，避免頻繁查詢
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    - 使用 dict 儲存狀態
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    - 設定過期時間
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 過期時重新並行查詢
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ttl_seconds</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">30.0</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">float</span><span class="p">]]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_ttl</span> <span class="o">=</span> <span class="n">ttl_seconds</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_status</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_all_statuses</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="練習-3實作帶有重試邏輯的並行下載器">練習 3：實作帶有重試邏輯的並行下載器</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">parallel_download</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">urls</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">max_concurrent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">max_retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">bytes</span> <span class="o">|</span> <span class="ne">Exception</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    並行下載多個 URL，支援重試
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 使用 Semaphore 限制並行數
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 實作指數退避（exponential backoff）
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 使用 aiohttp 進行非同步 HTTP 請求
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="練習-4實作-semaphore-限制並行數量的-taskgroup">練習 4：實作 semaphore 限制並行數量的 TaskGroup</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">BoundedTaskGroup</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    限制最大並行數的 TaskGroup
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用方式：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        async with BoundedTaskGroup(max_concurrent=5) as tg:
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">            for item in items:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">                tg.create_task(process(item))
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 結合 Semaphore 和 TaskGroup
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 保持 TaskGroup 的錯誤處理語義
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_concurrent</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_semaphore</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="n">max_concurrent</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_tg</span><span class="p">:</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="fm">__aenter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="fm">__aexit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">def</span> <span class="nf">create_task</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">coro</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-5實作監控儀表板">練習 5：實作監控儀表板</h4>
<p>建立一個即時監控多個 Git repo 狀態的工具：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">monitor_repos</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">repos</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">interval</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">5.0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">on_change</span><span class="p">:</span> <span class="n">callable</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    每隔 interval 秒並行檢查所有 repo
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    狀態變化時呼叫 on_change callback
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 使用 asyncio.sleep 控制間隔
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 比較前後狀態找出變化
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 支援 Ctrl+C 優雅退出
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.gather">asyncio.gather 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.TaskGroup">TaskGroup 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/asyncio-sync.html#asyncio.Semaphore">Semaphore 官方文件</a></li>
<li><a href="https://peps.python.org/pep-0654/">PEP 654 - Exception Groups</a> - TaskGroup 的異常處理基礎</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">非同步 Subprocess</a></em>
<em>下一章：<a href="/blog/python-advanced/01-asyncio/case-studies/sync-async-bridge/" data-link-title="案例：同步/非同步橋接" data-link-desc="用 run_in_executor 和 asyncio.run 在同步與非同步程式碼之間建立橋樑">同步/非同步橋接</a></em></p>
]]></content:encoded></item><item><title>3.4 GIL 與執行緒模型</title><link>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/gil-threading/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/gil-threading/</guid><description>&lt;p>GIL（Global Interpreter Lock）是 CPython 中最具爭議的設計之一。本章深入探討 GIL 的歷史、實現，以及 Python 3.13+ Free-threading 的技術細節。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/bytecode/" data-link-title="3.3 Bytecode 與虛擬機" data-link-desc="理解 Python 的執行過程">3.3 Bytecode 與虛擬機&lt;/a>&lt;/li>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理&lt;/a>&lt;/li>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/free-threading/" data-link-title="4.5 Free-Threading - Python 的真正多執行緒時代" data-link-desc="Python 3.13&amp;#43; 無 GIL 版本的完整指南">3.8 Free-Threading&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>理解 GIL 存在的歷史原因&lt;/li>
&lt;li>理解 GIL 的釋放時機&lt;/li>
&lt;li>理解 Free-threading 的實現挑戰&lt;/li>
&lt;li>做出正確的並行策略選擇&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="原理層為什麼需要-gil">【原理層】為什麼需要 GIL？&lt;/h2>
&lt;h3 id="歷史背景">歷史背景&lt;/h3>
&lt;p>GIL 是 1992 年 Python 初創時的設計決策：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">當時的考量：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">1. 單核 CPU 是主流
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">2. 簡化記憶體管理（參考計數）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">3. 簡化 C 擴展開發
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">4. 避免細粒度鎖的複雜性&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="參考計數與執行緒安全">參考計數與執行緒安全&lt;/h3>
&lt;p>GIL 主要保護參考計數操作：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">// 沒有 GIL 時，這個操作不是原子的
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nf">Py_INCREF&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1">// 展開後：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1">// 1. 讀取 ob_refcnt
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1">// 2. 加 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1">// 3. 寫回 ob_refcnt
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1">// 如果兩個執行緒同時執行，可能會：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1">// Thread 1: 讀取 refcnt = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1">// Thread 2: 讀取 refcnt = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1">// Thread 1: 寫入 refcnt = 2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1">// Thread 2: 寫入 refcnt = 2 ← 錯誤！應該是 3
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="gil-的實現">GIL 的實現&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">// 簡化版的 GIL 結構
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="n">PyMutex&lt;/span> &lt;span class="n">mutex&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 互斥鎖
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">PyThread_type_lock&lt;/span> &lt;span class="n">lock&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 執行緒鎖
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">locked&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 鎖定狀態
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="n">_gil_runtime_state&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="設計層gil-的釋放時機">【設計層】GIL 的釋放時機&lt;/h2>
&lt;h3 id="自動釋放">自動釋放&lt;/h3>
&lt;p>GIL 在以下情況會自動釋放：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 1. I/O 操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="n">f&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;file.txt&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;r&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 釋放 GIL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 釋放 GIL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 2. sleep&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 釋放 GIL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 3. 某些 C 擴展操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">numpy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">np&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># NumPy 可能釋放 GIL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 4. 定期釋放（每 N 個 bytecode 指令）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># Python 3.2+ 預設約每 5ms 檢查一次&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="檢查間隔">檢查間隔&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 查看切換間隔（秒）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getswitchinterval&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># 0.005（5ms）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 設定切換間隔&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">setswitchinterval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.001&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 1ms&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="c-擴展中手動釋放-gil">C 擴展中手動釋放 GIL&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">// C 擴展可以明確釋放 GIL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">static&lt;/span> &lt;span class="n">PyObject&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="nf">compute_intensive&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">PyObject&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">PyObject&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 釋放 GIL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">Py_BEGIN_ALLOW_THREADS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 這裡的程式碼可以並行執行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nf">do_heavy_computation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 重新獲取 GIL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">Py_END_ALLOW_THREADS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="n">Py_RETURN_NONE&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="實驗測量-gil-的影響">【實驗】測量 GIL 的影響&lt;/h2>
&lt;h3 id="cpu-密集任務">CPU 密集任務&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">threading&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">cpu_intensive&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;純 Python CPU 密集計算&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">total&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">total&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">i&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">total&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">benchmark&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">func&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">num_threads&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="n">threads&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_threads&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">t&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">threading&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">func&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">threads&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">threads&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5_000_000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="c1"># 單執行緒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="n">time_1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">benchmark&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpu_intensive&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,),&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;1 執行緒: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">time_1&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.3f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="c1"># 多執行緒（受 GIL 限制）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="n">time_4&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">benchmark&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpu_intensive&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,),&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;4 執行緒: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">time_4&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.3f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="c1"># 結果：多執行緒可能更慢（執行緒切換開銷）&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="io-密集任務">I/O 密集任務&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">threading&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">urllib.request&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">io_intensive&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;I/O 密集操作&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">urllib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">urlopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;https://example.com&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 單執行緒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">io_intensive&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="n">time_sequential&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;序列: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">time_sequential&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.3f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="c1"># 多執行緒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="n">threads&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">threading&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">io_intensive&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,))&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">threads&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">threads&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="n">time_parallel&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;並行: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">time_parallel&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.3f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="c1"># 結果：多執行緒明顯更快（I/O 時釋放 GIL）&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="深入free-threading-技術細節">【深入】Free-threading 技術細節&lt;/h2>
&lt;h3 id="biased-reference-counting">Biased Reference Counting&lt;/h3>
&lt;p>Python 3.13+ Free-threading 使用「偏向參考計數」解決多執行緒問題：&lt;/p></description><content:encoded><![CDATA[<p>GIL（Global Interpreter Lock）是 CPython 中最具爭議的設計之一。本章深入探討 GIL 的歷史、實現，以及 Python 3.13+ Free-threading 的技術細節。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/04-cpython-internals/bytecode/" data-link-title="3.3 Bytecode 與虛擬機" data-link-desc="理解 Python 的執行過程">3.3 Bytecode 與虛擬機</a></li>
<li>入門系列 <a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a></li>
<li>入門系列 <a href="/blog/python-advanced/04-cpython-internals/free-threading/" data-link-title="4.5 Free-Threading - Python 的真正多執行緒時代" data-link-desc="Python 3.13&#43; 無 GIL 版本的完整指南">3.8 Free-Threading</a></li>
</ul>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>理解 GIL 存在的歷史原因</li>
<li>理解 GIL 的釋放時機</li>
<li>理解 Free-threading 的實現挑戰</li>
<li>做出正確的並行策略選擇</li>
</ol>
<hr>
<h2 id="原理層為什麼需要-gil">【原理層】為什麼需要 GIL？</h2>
<h3 id="歷史背景">歷史背景</h3>
<p>GIL 是 1992 年 Python 初創時的設計決策：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">當時的考量：
</span></span><span class="line"><span class="ln">2</span><span class="cl">1. 單核 CPU 是主流
</span></span><span class="line"><span class="ln">3</span><span class="cl">2. 簡化記憶體管理（參考計數）
</span></span><span class="line"><span class="ln">4</span><span class="cl">3. 簡化 C 擴展開發
</span></span><span class="line"><span class="ln">5</span><span class="cl">4. 避免細粒度鎖的複雜性</span></span></code></pre></div><h3 id="參考計數與執行緒安全">參考計數與執行緒安全</h3>
<p>GIL 主要保護參考計數操作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 沒有 GIL 時，這個操作不是原子的
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="nf">Py_INCREF</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// 展開後：
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// 1. 讀取 ob_refcnt
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">// 2. 加 1
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// 3. 寫回 ob_refcnt
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// 如果兩個執行緒同時執行，可能會：
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">// Thread 1: 讀取 refcnt = 1
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// Thread 2: 讀取 refcnt = 1
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">// Thread 1: 寫入 refcnt = 2
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">// Thread 2: 寫入 refcnt = 2  ← 錯誤！應該是 3
</span></span></span></code></pre></div><h3 id="gil-的實現">GIL 的實現</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 簡化版的 GIL 結構
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">PyMutex</span> <span class="n">mutex</span><span class="p">;</span>           <span class="c1">// 互斥鎖
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>    <span class="n">PyThread_type_lock</span> <span class="n">lock</span><span class="p">;</span> <span class="c1">// 執行緒鎖
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>    <span class="kt">int</span> <span class="n">locked</span><span class="p">;</span>              <span class="c1">// 鎖定狀態
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">}</span> <span class="n">_gil_runtime_state</span><span class="p">;</span></span></span></code></pre></div><hr>
<h2 id="設計層gil-的釋放時機">【設計層】GIL 的釋放時機</h2>
<h3 id="自動釋放">自動釋放</h3>
<p>GIL 在以下情況會自動釋放：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 1. I/O 操作</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s1">&#39;file.txt&#39;</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">)</span>  <span class="c1"># 釋放 GIL</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>          <span class="c1"># 釋放 GIL</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 2. sleep</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 釋放 GIL</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 3. 某些 C 擴展操作</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">dot</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>  <span class="c1"># NumPy 可能釋放 GIL</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 4. 定期釋放（每 N 個 bytecode 指令）</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># Python 3.2+ 預設約每 5ms 檢查一次</span></span></span></code></pre></div><h3 id="檢查間隔">檢查間隔</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 查看切換間隔（秒）</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">getswitchinterval</span><span class="p">())</span>  <span class="c1"># 0.005（5ms）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 設定切換間隔</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">setswitchinterval</span><span class="p">(</span><span class="mf">0.001</span><span class="p">)</span>  <span class="c1"># 1ms</span></span></span></code></pre></div><h3 id="c-擴展中手動釋放-gil">C 擴展中手動釋放 GIL</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// C 擴展可以明確釋放 GIL
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">static</span> <span class="n">PyObject</span><span class="o">*</span> <span class="nf">compute_intensive</span><span class="p">(</span><span class="n">PyObject</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">PyObject</span><span class="o">*</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1">// 釋放 GIL
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>    <span class="n">Py_BEGIN_ALLOW_THREADS</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1">// 這裡的程式碼可以並行執行
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>    <span class="nf">do_heavy_computation</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1">// 重新獲取 GIL
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>    <span class="n">Py_END_ALLOW_THREADS</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">Py_RETURN_NONE</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="實驗測量-gil-的影響">【實驗】測量 GIL 的影響</h2>
<h3 id="cpu-密集任務">CPU 密集任務</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">cpu_intensive</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;純 Python CPU 密集計算&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">total</span> <span class="o">+=</span> <span class="n">i</span> <span class="o">*</span> <span class="n">i</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="n">total</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">num_threads</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">threads</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_threads</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">t</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">func</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">threads</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">t</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">n</span> <span class="o">=</span> <span class="mi">5_000_000</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"># 單執行緒</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="n">time_1</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">cpu_intensive</span><span class="p">,</span> <span class="p">(</span><span class="n">n</span><span class="p">,),</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;1 執行緒: </span><span class="si">{</span><span class="n">time_1</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"># 多執行緒（受 GIL 限制）</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="n">time_4</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">cpu_intensive</span><span class="p">,</span> <span class="p">(</span><span class="n">n</span><span class="p">,),</span> <span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;4 執行緒: </span><span class="si">{</span><span class="n">time_4</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="c1"># 結果：多執行緒可能更慢（執行緒切換開銷）</span></span></span></code></pre></div><h3 id="io-密集任務">I/O 密集任務</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">urllib.request</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">io_intensive</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;I/O 密集操作&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">with</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">except</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;https://example.com&#34;</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 單執行緒</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">io_intensive</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">time_sequential</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;序列: </span><span class="si">{</span><span class="n">time_sequential</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 多執行緒</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">threads</span> <span class="o">=</span> <span class="p">[</span><span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">io_intensive</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">url</span><span class="p">,))</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">t</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="n">time_parallel</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行: </span><span class="si">{</span><span class="n">time_parallel</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"># 結果：多執行緒明顯更快（I/O 時釋放 GIL）</span></span></span></code></pre></div><hr>
<h2 id="深入free-threading-技術細節">【深入】Free-threading 技術細節</h2>
<h3 id="biased-reference-counting">Biased Reference Counting</h3>
<p>Python 3.13+ Free-threading 使用「偏向參考計數」解決多執行緒問題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">傳統參考計數：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">┌─────────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│  ob_refcnt = 2                      │
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│  每次操作都需要原子操作或鎖         │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">└─────────────────────────────────────┘
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">偏向參考計數：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">┌─────────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│  local_refcnt[thread_id] = 1        │  ← 每個執行緒有自己的計數
</span></span><span class="line"><span class="ln">10</span><span class="cl">│  local_refcnt[thread_id] = 1        │
</span></span><span class="line"><span class="ln">11</span><span class="cl">│  shared_refcnt = 0                  │  ← 共享計數
</span></span><span class="line"><span class="ln">12</span><span class="cl">└─────────────────────────────────────┘
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">優點：
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 大多數操作只需更新區域計數（無鎖）
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 只有跨執行緒參考才需要更新共享計數</span></span></code></pre></div><h3 id="延遲參考計數">延遲參考計數</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Free-threading 中的優化策略</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 對於不朽物件（immortal objects）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 如 None、True、False、小整數</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 完全跳過參考計數</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 對於局部物件</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 使用區域計數，無需同步</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 只有跨執行緒共享的物件</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 才需要使用原子操作</span></span></span></code></pre></div><h3 id="記憶體模型變化">記憶體模型變化</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">傳統 CPython（有 GIL）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">┌──────────────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│  Thread 1  │  Thread 2  │  Thread 3      │
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│     ↓            ↓            ↓          │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│     └────────────┼────────────┘          │
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│                  ↓                       │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│               [ GIL ]                    │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│                  ↓                       │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│        [ Python Interpreter ]            │
</span></span><span class="line"><span class="ln">10</span><span class="cl">│                  ↓                       │
</span></span><span class="line"><span class="ln">11</span><span class="cl">│          [ 共享記憶體 ]                  │
</span></span><span class="line"><span class="ln">12</span><span class="cl">└──────────────────────────────────────────┘
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">Free-threaded CPython：
</span></span><span class="line"><span class="ln">15</span><span class="cl">┌──────────────────────────────────────────┐
</span></span><span class="line"><span class="ln">16</span><span class="cl">│  Thread 1  │  Thread 2  │  Thread 3      │
</span></span><span class="line"><span class="ln">17</span><span class="cl">│     ↓            ↓            ↓          │
</span></span><span class="line"><span class="ln">18</span><span class="cl">│  [Local]     [Local]     [Local]         │
</span></span><span class="line"><span class="ln">19</span><span class="cl">│  State       State       State           │
</span></span><span class="line"><span class="ln">20</span><span class="cl">│     ↓            ↓            ↓          │
</span></span><span class="line"><span class="ln">21</span><span class="cl">│     └────────────┼────────────┘          │
</span></span><span class="line"><span class="ln">22</span><span class="cl">│                  ↓                       │
</span></span><span class="line"><span class="ln">23</span><span class="cl">│     [ 原子操作 / 鎖 / 無鎖資料結構 ]     │
</span></span><span class="line"><span class="ln">24</span><span class="cl">│                  ↓                       │
</span></span><span class="line"><span class="ln">25</span><span class="cl">│          [ 共享記憶體 ]                  │
</span></span><span class="line"><span class="ln">26</span><span class="cl">└──────────────────────────────────────────┘</span></span></code></pre></div><hr>
<h2 id="實戰free-threading-程式設計">【實戰】Free-threading 程式設計</h2>
<h3 id="檢查執行環境">檢查執行環境</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">check_environment</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查 Free-threading 環境&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">gil_enabled</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">_is_gil_enabled</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;GIL 啟用: </span><span class="si">{</span><span class="n">gil_enabled</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="ow">not</span> <span class="n">gil_enabled</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;傳統 Python（有 GIL）&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">is_free_threaded</span> <span class="o">=</span> <span class="n">check_environment</span><span class="p">()</span></span></span></code></pre></div><h3 id="執行緒安全的程式設計">執行緒安全的程式設計</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 不安全：共享可變狀態</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">unsafe_increment</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">global</span> <span class="n">counter</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">counter</span> <span class="o">+=</span> <span class="mi">1</span>  <span class="c1"># 競爭條件！</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 安全：使用鎖</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">lock</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">safe_increment</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">global</span> <span class="n">counter</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">with</span> <span class="n">lock</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">counter</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"># 更好：使用原子操作或不可變資料</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">Counter</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">def</span> <span class="nf">better_approach</span><span class="p">(</span><span class="n">data_chunk</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;每個執行緒處理自己的資料，最後合併&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">local_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">data_chunk</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">local_count</span> <span class="o">+=</span> <span class="n">process</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">return</span> <span class="n">local_count</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">better_approach</span><span class="p">,</span> <span class="n">data_chunks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">results</span><span class="p">)</span></span></span></code></pre></div><h3 id="適應性程式碼">適應性程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">compute</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;根據環境選擇策略&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">free_threaded</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">sys</span><span class="p">,</span> <span class="s1">&#39;_is_gil_enabled&#39;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="kc">True</span><span class="p">)()</span> <span class="o">==</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">if</span> <span class="n">free_threaded</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># 可以安全使用多執行緒</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">return</span> <span class="n">parallel_compute_threading</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># 使用多進程或保持單執行緒</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">return</span> <span class="n">parallel_compute_multiprocess</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></span></span></code></pre></div><hr>
<h2 id="選擇指南並行策略">【選擇指南】並行策略</h2>
<h3 id="決策流程">決策流程</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">你的任務是什麼類型？
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">│
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── I/O 密集（網路、檔案、資料庫）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│   └── 使用 threading 或 asyncio
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│       （GIL 不影響）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">└── CPU 密集（計算、處理）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    ├── 使用 Free-threaded Python (3.13+)?
</span></span><span class="line"><span class="ln">10</span><span class="cl">    │   ├── 是 → 可以使用 threading
</span></span><span class="line"><span class="ln">11</span><span class="cl">    │   └── 否 → 選擇以下方案
</span></span><span class="line"><span class="ln">12</span><span class="cl">    │
</span></span><span class="line"><span class="ln">13</span><span class="cl">    ├── 可以用 C 擴展？
</span></span><span class="line"><span class="ln">14</span><span class="cl">    │   └── NumPy、Cython 等（會釋放 GIL）
</span></span><span class="line"><span class="ln">15</span><span class="cl">    │
</span></span><span class="line"><span class="ln">16</span><span class="cl">    └── 純 Python？
</span></span><span class="line"><span class="ln">17</span><span class="cl">        └── 使用 multiprocessing 或 ProcessPoolExecutor</span></span></code></pre></div><h3 id="效能比較總結">效能比較總結</h3>
<table>
  <thead>
      <tr>
          <th>任務類型</th>
          <th>threading (有 GIL)</th>
          <th>threading (Free)</th>
          <th>multiprocessing</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>I/O 密集</td>
          <td>好</td>
          <td>好</td>
          <td>過重</td>
      </tr>
      <tr>
          <td>CPU 密集</td>
          <td>無效</td>
          <td>好</td>
          <td>好</td>
      </tr>
      <tr>
          <td>記憶體共享</td>
          <td>簡單</td>
          <td>簡單</td>
          <td>複雜</td>
      </tr>
      <tr>
          <td>啟動成本</td>
          <td>低</td>
          <td>低</td>
          <td>高</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="未來gil-的發展">【未來】GIL 的發展</h2>
<h3 id="路線圖">路線圖</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Python 3.13 (2024): 實驗性 Free-threading
</span></span><span class="line"><span class="ln">2</span><span class="cl">Python 3.14 (2025): 正式支援 Free-threading
</span></span><span class="line"><span class="ln">3</span><span class="cl">Python 3.15/3.16:   可能成為預設
</span></span><span class="line"><span class="ln">4</span><span class="cl">未來:               GIL 可能完全移除</span></span></code></pre></div><h3 id="生態系統遷移">生態系統遷移</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 檢查套件是否支援 Free-threading</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># pip index versions package-name</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 主要框架的支援狀態（2025年底）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># NumPy 2.1+:       支援</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># pandas 2.2+:      支援</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># scikit-learn 1.6+: 支援</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># PyTorch 2.6+:     支援</span></span></span></code></pre></div><hr>
<h2 id="思考題">思考題</h2>
<ol>
<li>如果沒有 GIL，CPython 需要做哪些改變來保證記憶體安全？</li>
<li>為什麼其他 Python 實現（如 Jython、IronPython）沒有 GIL？</li>
<li>Free-threading 的效能損失主要來自哪裡？如何最小化？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>寫一個程式，測量 GIL 切換間隔對效能的影響</li>
<li>比較 Free-threaded 和傳統 Python 在相同 CPU 密集任務上的效能</li>
<li>將一個使用 multiprocessing 的程式改寫為 Free-threading 版本</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://peps.python.org/pep-0703/">PEP 703 - Making the Global Interpreter Lock Optional</a></li>
<li><a href="https://py-free-threading.github.io/">Python Free-Threading Guide</a></li>
<li><a href="https://www.dabeaz.com/python/UnderstandingGIL.pdf">Understanding the Python GIL - David Beazley</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/04-cpython-internals/bytecode/" data-link-title="3.3 Bytecode 與虛擬機" data-link-desc="理解 Python 的執行過程">Bytecode 與虛擬機</a></em>
<em>下一模組：<a href="/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組四：用 C 擴展 Python</a></em></p>
]]></content:encoded></item><item><title>從 threading 到 asyncio：轉換指南</title><link>https://tarrragon.github.io/blog/python-advanced/01-asyncio/threading-to-asyncio/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/01-asyncio/threading-to-asyncio/</guid><description>&lt;p>如果你已經熟悉入門系列的 &lt;code>threading&lt;/code> 模組，本章將幫助你理解為什麼需要 asyncio，以及如何將現有的多執行緒程式碼轉換為異步版本。&lt;/p>
&lt;h2 id="為什麼要從-threading-轉向-asyncio">為什麼要從 threading 轉向 asyncio？&lt;/h2>
&lt;h3 id="threading-的限制">threading 的限制&lt;/h3>
&lt;p>&lt;code>threading&lt;/code> 是處理並發的傳統方案，但它有幾個固有限制：&lt;/p>
&lt;h4 id="1-資源消耗高">1. 資源消耗高&lt;/h4>
&lt;p>每個執行緒都需要分配記憶體（預設約 8MB stack）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">threading&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 建立 100 個執行緒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">threads&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">threading&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">some_task&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 記憶體消耗：約 800MB 的 stack 空間&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>當需要處理數千個並發連線時，執行緒模型會面臨資源瓶頸。&lt;/p>
&lt;h4 id="2-上下文切換成本">2. 上下文切換成本&lt;/h4>
&lt;p>作業系統需要在執行緒之間切換，這涉及：&lt;/p>
&lt;ul>
&lt;li>保存和恢復 CPU 暫存器&lt;/li>
&lt;li>切換記憶體映射&lt;/li>
&lt;li>快取失效&lt;/li>
&lt;/ul>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">執行緒 1 執行 → 上下文切換（耗時）→ 執行緒 2 執行 → 上下文切換（耗時）→ ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="3-gil-的限制">3. GIL 的限制&lt;/h4>
&lt;p>由於 GIL，多個執行緒無法真正並行執行 Python 程式碼：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 這段程式碼實際上是序列執行的&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">cpu_task&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="n">total&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000000&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="n">total&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">i&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">total&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 即使使用多執行緒，也無法利用多核 CPU&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="n">threads&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">threading&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">cpu_task&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="asyncio-的優勢">asyncio 的優勢&lt;/h3>
&lt;p>asyncio 採用不同的並發模型來解決這些問題：&lt;/p>
&lt;h4 id="1-輕量級協程">1. 輕量級協程&lt;/h4>
&lt;p>協程只是普通的 Python 物件，記憶體消耗極低：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 建立 10000 個協程&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">some_task&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 記憶體消耗：幾十 KB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">some_task&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10000&lt;/span>&lt;span class="p">)]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="2-協作式切換">2. 協作式切換&lt;/h4>
&lt;p>協程只在 &lt;code>await&lt;/code> 點切換，沒有強制的上下文切換：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">協程 1 執行 → await（主動讓出）→ 協程 2 執行 → await（主動讓出）→ ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="3-單執行緒高並發">3. 單執行緒高並發&lt;/h4>
&lt;p>asyncio 在單執行緒中處理所有任務，避免了執行緒同步問題：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">handle_request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">client&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 等待時處理其他請求&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 等待時處理其他請求&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="並發模型比較">並發模型比較&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>特性&lt;/th>
 &lt;th>threading&lt;/th>
 &lt;th>asyncio&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>執行模型&lt;/td>
 &lt;td>多執行緒並行&lt;/td>
 &lt;td>單執行緒協作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>切換方式&lt;/td>
 &lt;td>作業系統搶佔&lt;/td>
 &lt;td>程式主動讓出&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>記憶體消耗&lt;/td>
 &lt;td>高（每執行緒 ~8MB）&lt;/td>
 &lt;td>低（每協程 ~KB）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>並發數量&lt;/td>
 &lt;td>百級&lt;/td>
 &lt;td>萬級&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>GIL 影響&lt;/td>
 &lt;td>受限制&lt;/td>
 &lt;td>無影響（不需要多核）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同步複雜度&lt;/td>
 &lt;td>需要鎖（Lock、Semaphore）&lt;/td>
 &lt;td>較少（單執行緒）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適用場景&lt;/td>
 &lt;td>I/O 密集 + 共享記憶體&lt;/td>
 &lt;td>I/O 密集 + 大規模並發&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="程式碼轉換模式">程式碼轉換模式&lt;/h2>
&lt;h3 id="模式-1簡單函式轉換">模式 1：簡單函式轉換&lt;/h3>
&lt;p>&lt;strong>threading 版本&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">threading&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">fetch_data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 模擬網路延遲&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Data from &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;url1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;url2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;url3&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">threads&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">t&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">threading&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">u&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fetch_data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">threads&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">threads&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>asyncio 版本&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">fetch_data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 非阻塞等待&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Data from &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;url1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;url2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;url3&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 使用 gather 並發執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">fetch_data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">main&lt;/span>&lt;span class="p">())&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>轉換要點&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>如果你已經熟悉入門系列的 <code>threading</code> 模組，本章將幫助你理解為什麼需要 asyncio，以及如何將現有的多執行緒程式碼轉換為異步版本。</p>
<h2 id="為什麼要從-threading-轉向-asyncio">為什麼要從 threading 轉向 asyncio？</h2>
<h3 id="threading-的限制">threading 的限制</h3>
<p><code>threading</code> 是處理並發的傳統方案，但它有幾個固有限制：</p>
<h4 id="1-資源消耗高">1. 資源消耗高</h4>
<p>每個執行緒都需要分配記憶體（預設約 8MB stack）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 建立 100 個執行緒</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">threads</span> <span class="o">=</span> <span class="p">[</span><span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">some_task</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 記憶體消耗：約 800MB 的 stack 空間</span></span></span></code></pre></div><p>當需要處理數千個並發連線時，執行緒模型會面臨資源瓶頸。</p>
<h4 id="2-上下文切換成本">2. 上下文切換成本</h4>
<p>作業系統需要在執行緒之間切換，這涉及：</p>
<ul>
<li>保存和恢復 CPU 暫存器</li>
<li>切換記憶體映射</li>
<li>快取失效</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">執行緒 1 執行 → 上下文切換（耗時）→ 執行緒 2 執行 → 上下文切換（耗時）→ ...</span></span></code></pre></div><h4 id="3-gil-的限制">3. GIL 的限制</h4>
<p>由於 GIL，多個執行緒無法真正並行執行 Python 程式碼：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 這段程式碼實際上是序列執行的</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">cpu_task</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="n">total</span> <span class="o">+=</span> <span class="n">i</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">return</span> <span class="n">total</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 即使使用多執行緒，也無法利用多核 CPU</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="n">threads</span> <span class="o">=</span> <span class="p">[</span><span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">cpu_task</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">)]</span></span></span></code></pre></div><h3 id="asyncio-的優勢">asyncio 的優勢</h3>
<p>asyncio 採用不同的並發模型來解決這些問題：</p>
<h4 id="1-輕量級協程">1. 輕量級協程</h4>
<p>協程只是普通的 Python 物件，記憶體消耗極低：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 建立 10000 個協程</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">some_task</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 記憶體消耗：幾十 KB</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">some_task</span><span class="p">()</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10000</span><span class="p">)]</span></span></span></code></pre></div><h4 id="2-協作式切換">2. 協作式切換</h4>
<p>協程只在 <code>await</code> 點切換，沒有強制的上下文切換：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">協程 1 執行 → await（主動讓出）→ 協程 2 執行 → await（主動讓出）→ ...</span></span></code></pre></div><h4 id="3-單執行緒高並發">3. 單執行緒高並發</h4>
<p>asyncio 在單執行緒中處理所有任務，避免了執行緒同步問題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">handle_request</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>    <span class="c1"># 等待時處理其他請求</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>    <span class="c1"># 等待時處理其他請求</span></span></span></code></pre></div><h2 id="並發模型比較">並發模型比較</h2>
<table>
  <thead>
      <tr>
          <th>特性</th>
          <th>threading</th>
          <th>asyncio</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>執行模型</td>
          <td>多執行緒並行</td>
          <td>單執行緒協作</td>
      </tr>
      <tr>
          <td>切換方式</td>
          <td>作業系統搶佔</td>
          <td>程式主動讓出</td>
      </tr>
      <tr>
          <td>記憶體消耗</td>
          <td>高（每執行緒 ~8MB）</td>
          <td>低（每協程 ~KB）</td>
      </tr>
      <tr>
          <td>並發數量</td>
          <td>百級</td>
          <td>萬級</td>
      </tr>
      <tr>
          <td>GIL 影響</td>
          <td>受限制</td>
          <td>無影響（不需要多核）</td>
      </tr>
      <tr>
          <td>同步複雜度</td>
          <td>需要鎖（Lock、Semaphore）</td>
          <td>較少（單執行緒）</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>I/O 密集 + 共享記憶體</td>
          <td>I/O 密集 + 大規模並發</td>
      </tr>
  </tbody>
</table>
<h2 id="程式碼轉換模式">程式碼轉換模式</h2>
<h3 id="模式-1簡單函式轉換">模式 1：簡單函式轉換</h3>
<p><strong>threading 版本</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">fetch_data</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 模擬網路延遲</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Data from </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;url1&#34;</span><span class="p">,</span> <span class="s2">&#34;url2&#34;</span><span class="p">,</span> <span class="s2">&#34;url3&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">threads</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">t</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="k">lambda</span> <span class="n">u</span><span class="o">=</span><span class="n">url</span><span class="p">:</span> <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">fetch_data</span><span class="p">(</span><span class="n">u</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">threads</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">t</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><p><strong>asyncio 版本</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch_data</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 非阻塞等待</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Data from </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;url1&#34;</span><span class="p">,</span> <span class="s2">&#34;url2&#34;</span><span class="p">,</span> <span class="s2">&#34;url3&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 使用 gather 並發執行</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch_data</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 執行</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><p><strong>轉換要點</strong>：</p>
<table>
  <thead>
      <tr>
          <th>原本</th>
          <th>轉換後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>def</code></td>
          <td><code>async def</code></td>
      </tr>
      <tr>
          <td><code>time.sleep()</code></td>
          <td><code>await asyncio.sleep()</code></td>
      </tr>
      <tr>
          <td><code>threading.Thread</code> + <code>join</code></td>
          <td><code>asyncio.gather()</code></td>
      </tr>
  </tbody>
</table>
<h3 id="模式-2threadpoolexecutor-轉換">模式 2：ThreadPoolExecutor 轉換</h3>
<p><strong>threading 版本</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">process_file</span><span class="p">(</span><span class="n">filepath</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">process_file</span><span class="p">,</span> <span class="n">file_paths</span><span class="p">))</span></span></span></code></pre></div><p><strong>asyncio 版本</strong>（使用 aiofiles）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">aiofiles</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">process_file</span><span class="p">(</span><span class="n">filepath</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">aiofiles</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="k">await</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">process_file</span><span class="p">(</span><span class="n">fp</span><span class="p">)</span> <span class="k">for</span> <span class="n">fp</span> <span class="ow">in</span> <span class="n">file_paths</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><h3 id="模式-3保留同步程式碼混合模式">模式 3：保留同步程式碼（混合模式）</h3>
<p>有時候你無法（或不想）將所有程式碼都轉為異步。asyncio 提供了 <code>run_in_executor</code> 來處理這種情況：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 保持原有的同步函式</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">blocking_operation</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># 這是一個阻塞的第三方函式庫呼叫</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Processed: </span><span class="si">{</span><span class="n">data</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># 在執行緒池中執行同步函式</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">pool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">pool</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="n">blocking_operation</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="s2">&#34;my_data&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><p>這種模式讓你可以漸進式地將程式碼遷移到 asyncio。</p>
<h2 id="何時選擇哪種方案">何時選擇哪種方案？</h2>
<h3 id="選擇-threading">選擇 threading</h3>
<ul>
<li>需要與不支援 asyncio 的函式庫整合</li>
<li>需要共享記憶體且修改頻繁</li>
<li>並發數量較少（&lt; 100）</li>
<li>團隊對 threading 更熟悉</li>
</ul>
<h3 id="選擇-asyncio">選擇 asyncio</h3>
<ul>
<li>需要處理大量並發連線（Web 伺服器、聊天室）</li>
<li>主要是 I/O 操作（網路、檔案）</li>
<li>使用現代 async 函式庫（aiohttp、httpx、asyncpg）</li>
<li>需要高效能的單機並發</li>
</ul>
<h3 id="選擇-multiprocessing">選擇 multiprocessing</h3>
<ul>
<li>CPU 密集任務（資料處理、科學計算）</li>
<li>需要真正的並行計算</li>
<li>各任務相對獨立，不需要頻繁通訊</li>
</ul>
<h2 id="決策流程圖">決策流程圖</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">任務類型是什麼？
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    ├─ CPU 密集 ────────────────→ multiprocessing
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    └─ I/O 密集
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        ├─ 並發數 &gt; 100 ─────────→ asyncio
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        ├─ 需要共享記憶體 ────────→ threading
</span></span><span class="line"><span class="ln">10</span><span class="cl">        │
</span></span><span class="line"><span class="ln">11</span><span class="cl">        └─ 第三方函式庫支援 async？
</span></span><span class="line"><span class="ln">12</span><span class="cl">            │
</span></span><span class="line"><span class="ln">13</span><span class="cl">            ├─ 是 ───────────────→ asyncio
</span></span><span class="line"><span class="ln">14</span><span class="cl">            │
</span></span><span class="line"><span class="ln">15</span><span class="cl">            └─ 否 ───────────────→ threading 或 asyncio + run_in_executor</span></span></code></pre></div><h2 id="常見轉換陷阱">常見轉換陷阱</h2>
<h3 id="陷阱-1忘記-await">陷阱 1：忘記 await</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 錯誤：忘記 await</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">fetch_data</span><span class="p">(</span><span class="s2">&#34;url&#34;</span><span class="p">)</span>  <span class="c1"># 這只會建立協程物件，不會執行</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>  <span class="c1"># &lt;coroutine object fetch_data at 0x...&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 正確</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch_data</span><span class="p">(</span><span class="s2">&#34;url&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span></span></span></code></pre></div><h3 id="陷阱-2在異步函式中使用阻塞呼叫">陷阱 2：在異步函式中使用阻塞呼叫</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 錯誤：使用阻塞的 time.sleep</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">bad_sleep</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 這會阻塞整個事件迴圈！</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 正確：使用非阻塞的 asyncio.sleep</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">good_sleep</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 這會讓出控制權</span></span></span></code></pre></div><h3 id="陷阱-3在同步函式中呼叫異步函式">陷阱 3：在同步函式中呼叫異步函式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 錯誤：在普通函式中直接呼叫</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">sync_function</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch_data</span><span class="p">(</span><span class="s2">&#34;url&#34;</span><span class="p">)</span>  <span class="c1"># SyntaxError!</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 正確：使用 asyncio.run</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">def</span> <span class="nf">sync_function</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">fetch_data</span><span class="p">(</span><span class="s2">&#34;url&#34;</span><span class="p">))</span></span></span></code></pre></div><h2 id="實戰練習">實戰練習</h2>
<h3 id="練習-1轉換簡單的多執行緒下載器">練習 1：轉換簡單的多執行緒下載器</h3>
<p>將以下 threading 程式碼轉換為 asyncio 版本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">download</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Downloading </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s2">...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>  <span class="c1"># 模擬下載</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Finished </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Content of </span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;url1&#34;</span><span class="p">,</span> <span class="s2">&#34;url2&#34;</span><span class="p">,</span> <span class="s2">&#34;url3&#34;</span><span class="p">,</span> <span class="s2">&#34;url4&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">threads</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">t</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">download</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">url</span><span class="p">,))</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">threads</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">t</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">main</span><span class="p">()</span></span></span></code></pre></div><h3 id="練習-2使用-run_in_executor-整合同步函式庫">練習 2：使用 run_in_executor 整合同步函式庫</h3>
<p>假設你有一個只支援同步的第三方 API 客戶端，寫一個異步包裝器。</p>
<h2 id="下一步">下一步</h2>
<p>理解了 threading 和 asyncio 的區別後，你可以開始深入學習 asyncio 的核心概念：</p>
<ul>
<li><a href="/blog/python-advanced/01-asyncio/fundamentals/" data-link-title="1.1 基礎概念與事件迴圈" data-link-desc="理解 asyncio 的核心概念：事件迴圈、協程與並發模型">1.1 基礎概念與事件迴圈</a> - 理解 asyncio 的運作原理</li>
<li><a href="/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">1.2 協程與 Task 管理</a> - 掌握 async/await 語法</li>
<li><a href="/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">1.4 實戰：與同步程式碼整合</a> - 學習混合模式的最佳實踐</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">入門系列 3.7 並行處理</a></em>
<em>下一章：<a href="/blog/python-advanced/01-asyncio/fundamentals/" data-link-title="1.1 基礎概念與事件迴圈" data-link-desc="理解 asyncio 的核心概念：事件迴圈、協程與並發模型">1.1 基礎概念與事件迴圈</a></em></p>
]]></content:encoded></item></channel></rss>