[{"data":1,"prerenderedAt":3576},["ShallowReactive",2],{"blog-concurrency":3,"related-concurrency":1500},{"id":4,"title":5,"author":6,"body":7,"category":1486,"date":1487,"description":1488,"draft":1489,"extension":1490,"image":338,"meta":1491,"navigation":391,"path":1492,"seo":1493,"stem":1494,"tags":1495,"__hash__":1499},"blog/blog/concurrency.md","Python 並發處理方法","Ting Zhang",{"type":8,"value":9,"toc":1465},"minimark",[10,15,19,22,25,30,80,84,119,123,156,160,178,181,329,332,1034,1037,1289,1293,1299,1317,1321,1326,1344,1347,1352,1356,1359,1363,1366,1380,1384,1387,1433,1436,1461],[11,12,14],"h2",{"id":13},"這篇文章的用處","這篇文章的用處？",[16,17,18],"p",{},"若專案內有遇到效能瓶頸，這可能可以幫助你加速。",[16,20,21],{},"Python 提供了幾種並發方式，每種方式適用於不同類型的任務。",[11,23,24],{"id":24},"各方式介紹",[26,27,29],"h3",{"id":28},"_1-多線程multithreading","1. 多線程（Multithreading）",[31,32,33,41,62,68,74],"ul",{},[34,35,36,40],"li",{},[37,38,39],"strong",{},"特點","：由於 GIL 的存在，多線程無法實現真正的並行，線程間由解釋器切換執行。",[34,42,43,46,47,50,51],{},[37,44,45],{},"適用場景","：適合 ",[37,48,49],{},"I/O 綁定任務","，如：\n",[31,52,53,56,59],{},[34,54,55],{},"API 呼叫",[34,57,58],{},"檔案讀寫",[34,60,61],{},"使用者輸入等待",[34,63,64,67],{},[37,65,66],{},"限制","：在 CPU 綁定任務（如計算 Fibonacci 數列）中，效能與單線程迴圈相差無幾。",[34,69,70,73],{},[37,71,72],{},"優點","：線程間資料共享簡單，記憶體開銷低。",[34,75,76,79],{},[37,77,78],{},"缺點","：受 GIL 限制，無法利用多核心。",[26,81,83],{"id":82},"_2-多進程multiprocessing","2. 多進程（Multiprocessing）",[31,85,86,91,109,114],{},[34,87,88,90],{},[37,89,39],{},"：每個進程擁有獨立的 Python 解釋器和 GIL，因此可以在不同 CPU 核心上實現真正的並行。",[34,92,93,46,95,50,98],{},[37,94,45],{},[37,96,97],{},"CPU 綁定任務",[31,99,100,103,106],{},[34,101,102],{},"數學運算",[34,104,105],{},"圖像處理",[34,107,108],{},"日誌分析",[34,110,111,113],{},[37,112,72],{},"：繞過 GIL，實現真並行，速度較快。",[34,115,116,118],{},[37,117,78],{},"：記憶體使用量高，進程間資料共享較複雜（需使用管道或共享記憶體）。",[26,120,122],{"id":121},"_3-asyncio","3. Asyncio",[31,124,125,130,146,151],{},[34,126,127,129],{},[37,128,39],{},"：使用協程（coroutines）實現非阻塞的並發，適合 I/O 綁定任務。",[34,131,132,134,135],{},[37,133,45],{},"：\n",[31,136,137,140,143],{},[34,138,139],{},"網路請求",[34,141,142],{},"檔案 I/O",[34,144,145],{},"其他等待外部資源的任務",[34,147,148,150],{},[37,149,72],{},"：輕量，記憶體使用效率高。",[34,152,153,155],{},[37,154,78],{},"：不適合 CPU 密集型任務，因為它並非真正的並行。",[26,157,159],{"id":158},"_4-第三方庫","4. 第三方庫",[31,161,162,168,173],{},[34,163,164,167],{},[37,165,166],{},"例子","：Cython、NumPy、Pandas、PyTorch 等。",[34,169,170,172],{},[37,171,39],{},"：這些庫通常使用 C 語言實現，部分操作可繞過 GIL，提供高效能。",[34,174,175,177],{},[37,176,45],{},"：特定計算密集型任務（如矩陣運算）。",[11,179,180],{"id":180},"特性比較表",[182,183,184,206],"table",{},[185,186,187],"thead",{},[188,189,190,194,197,200,203],"tr",{},[191,192,193],"th",{},"特性",[191,195,196],{},"Multi-threading",[191,198,199],{},"Multi-processing",[191,201,202],{},"AsyncIO",[191,204,205],{},"Disabled GIL",[207,208,209,227,244,262,279,295,310],"tbody",{},[188,210,211,217,220,223,225],{},[212,213,214],"td",{},[37,215,216],{},"並行",[212,218,219],{},"❌",[212,221,222],{},"✅",[212,224,219],{},[212,226,222],{},[188,228,229,234,237,240,242],{},[212,230,231],{},[37,232,233],{},"記憶體共享",[212,235,236],{},"容易",[212,238,239],{},"困難",[212,241,236],{},[212,243,236],{},[188,245,246,251,254,257,259],{},[212,247,248],{},[37,249,250],{},"記憶體使用",[212,252,253],{},"低",[212,255,256],{},"高",[212,258,253],{},[212,260,261],{},"中等",[188,263,264,269,272,275,277],{},[212,265,266],{},[37,267,268],{},"CPU 密集型",[212,270,271],{},"差",[212,273,274],{},"好",[212,276,271],{},[212,278,274],{},[188,280,281,286,288,290,293],{},[212,282,283],{},[37,284,285],{},"I/O 密集型",[212,287,274],{},[212,289,261],{},[212,291,292],{},"最佳",[212,294,274],{},[188,296,297,302,304,306,308],{},[212,298,299],{},[37,300,301],{},"實作複雜度",[212,303,261],{},[212,305,256],{},[212,307,261],{},[212,309,256],{},[188,311,312,317,320,323,326],{},[212,313,314],{},[37,315,316],{},"競爭條件風險",[212,318,319],{},"低（GIL保護）",[212,321,322],{},"低（隔離）",[212,324,325],{},"無（單執行緒）",[212,327,328],{},"高（需手動處理）",[11,330,331],{"id":331},"範例程式碼",[333,334,339],"pre",{"className":335,"code":336,"language":337,"meta":338,"style":338},"language-python shiki shiki-themes github-light github-dark","import time\nimport threading\nimport multiprocessing\nimport asyncio\nimport sys\nfrom concurrent.futures import ThreadPoolExecutor\nfrom functools import wraps\n\n# 計時裝飾器，用於測量執行時間\ndef timing_decorator(func):\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        result = func(*args, **kwargs)\n        end = time.time()\n        print(f\"{func.__name__} 執行時間: {end - start:.4f} 秒\")\n        return result\n    return wrapper\n\n# CPU 密集型任務：計算 Fibonacci 數列\ndef fib(n):\n    if n \u003C= 1:\n        return n\n    return fib(n - 1) + fib(n - 2)\n\n# I/O 綁定任務：模擬網路請求（全局函數）\ndef sync_io_task():\n    time.sleep(1)  # 模擬 1 秒的 I/O 延遲\n\n# 異步 I/O 任務\nasync def io_bound_task():\n    await asyncio.sleep(1)  # 模擬 1 秒的 I/O 延遲\n    return \"I/O 任務完成\"\n\n# 多線程實現\n@timing_decorator\ndef run_multithreading(n_tasks, task_type=\"cpu\"):\n    threads = []\n    if task_type == \"cpu\":\n        for _ in range(n_tasks):\n            t = threading.Thread(target=fib, args=(35,))\n            threads.append(t)\n            t.start()\n        for t in threads:\n            t.join()\n    else:  # I/O 任務\n        for _ in range(n_tasks):\n            t = threading.Thread(target=sync_io_task)\n            threads.append(t)\n            t.start()\n        for t in threads:\n            t.join()\n\n# 多進程實現\n@timing_decorator\ndef run_multiprocessing(n_tasks, task_type=\"cpu\"):\n    processes = []\n    if task_type == \"cpu\":\n        for _ in range(n_tasks):\n            p = multiprocessing.Process(target=fib, args=(35,))\n            processes.append(p)\n            p.start()\n        for p in processes:\n            p.join()\n    else:  # I/O 任務\n        for _ in range(n_tasks):\n            p = multiprocessing.Process(target=sync_io_task)\n            processes.append(p)\n            p.start()\n        for p in processes:\n            p.join()\n\n# Asyncio 實現\n@timing_decorator\nasync def run_asyncio(n_tasks):\n    tasks = [io_bound_task() for _ in range(n_tasks)]\n    await asyncio.gather(*tasks)\n\n# 禁用 GIL 的多線程實現（需 Python 3.13 --disable-gil）\n@timing_decorator\ndef run_nogil_multithreading(n_tasks, task_type=\"cpu\"):\n    with ThreadPoolExecutor(max_workers=n_tasks) as executor:\n        if task_type == \"cpu\":\n            futures = [executor.submit(fib, 35) for _ in range(n_tasks)]\n        else:  # I/O 任務\n            futures = [executor.submit(sync_io_task) for _ in range(n_tasks)]\n        for future in futures:\n            future.result()\n\n# 主程式\nif __name__ == \"__main__\":\n    n_tasks = 4  # 任務數量\n\n    print(\"=== CPU 密集型任務 (計算 Fibonacci 數列) ===\")\n    print(\"多線程 (Multithreading):\")\n    run_multithreading(n_tasks, task_type=\"cpu\")\n\n    print(\"\\n多進程 (Multiprocessing):\")\n    run_multiprocessing(n_tasks, task_type=\"cpu\")\n\n    if sys.version_info >= (3, 13) and hasattr(sys, \"disable_gil\"):\n        print(\"\\n禁用 GIL 的多線程:\")\n        run_nogil_multithreading(n_tasks, task_type=\"cpu\")\n    else:\n        print(\"\\n禁用 GIL 的多線程: 需要 Python 3.13 並啟用 --disable-gil\")\n\n    print(\"\\n=== I/O 綁定任務 (模擬網路請求) ===\")\n    print(\"多線程 (Multithreading):\")\n    run_multithreading(n_tasks, task_type=\"io\")\n\n    print(\"\\n多進程 (Multiprocessing):\")\n    run_multiprocessing(n_tasks, task_type=\"io\")\n\n    print(\"\\nAsyncio:\")\n    asyncio.run(run_asyncio(n_tasks))\n\n    if sys.version_info >= (3, 13) and hasattr(sys, \"disable_gil\"):\n        print(\"\\n禁用 GIL 的多線程:\")\n        run_nogil_multithreading(n_tasks, task_type=\"io\")\n    else:\n        print(\"\\n禁用 GIL 的多線程: 需要 Python 3.13 並啟用 --disable-gil\")\n","python","",[340,341,342,350,356,362,368,374,380,386,393,399,405,411,417,423,429,435,441,447,453,458,464,470,476,482,488,493,499,505,511,516,522,528,534,540,545,551,557,563,569,575,581,587,593,599,605,611,617,622,628,633,638,643,648,653,659,664,670,676,681,686,692,698,704,710,716,721,726,732,737,742,747,752,757,763,768,774,780,786,791,797,802,808,814,820,826,832,838,844,850,855,861,867,873,878,884,890,896,901,907,913,918,924,930,936,942,948,953,959,964,970,975,980,986,991,997,1003,1008,1013,1018,1024,1029],"code",{"__ignoreMap":338},[343,344,347],"span",{"class":345,"line":346},"line",1,[343,348,349],{},"import time\n",[343,351,353],{"class":345,"line":352},2,[343,354,355],{},"import threading\n",[343,357,359],{"class":345,"line":358},3,[343,360,361],{},"import multiprocessing\n",[343,363,365],{"class":345,"line":364},4,[343,366,367],{},"import asyncio\n",[343,369,371],{"class":345,"line":370},5,[343,372,373],{},"import sys\n",[343,375,377],{"class":345,"line":376},6,[343,378,379],{},"from concurrent.futures import ThreadPoolExecutor\n",[343,381,383],{"class":345,"line":382},7,[343,384,385],{},"from functools import wraps\n",[343,387,389],{"class":345,"line":388},8,[343,390,392],{"emptyLinePlaceholder":391},true,"\n",[343,394,396],{"class":345,"line":395},9,[343,397,398],{},"# 計時裝飾器，用於測量執行時間\n",[343,400,402],{"class":345,"line":401},10,[343,403,404],{},"def timing_decorator(func):\n",[343,406,408],{"class":345,"line":407},11,[343,409,410],{},"    @wraps(func)\n",[343,412,414],{"class":345,"line":413},12,[343,415,416],{},"    def wrapper(*args, **kwargs):\n",[343,418,420],{"class":345,"line":419},13,[343,421,422],{},"        start = time.time()\n",[343,424,426],{"class":345,"line":425},14,[343,427,428],{},"        result = func(*args, **kwargs)\n",[343,430,432],{"class":345,"line":431},15,[343,433,434],{},"        end = time.time()\n",[343,436,438],{"class":345,"line":437},16,[343,439,440],{},"        print(f\"{func.__name__} 執行時間: {end - start:.4f} 秒\")\n",[343,442,444],{"class":345,"line":443},17,[343,445,446],{},"        return result\n",[343,448,450],{"class":345,"line":449},18,[343,451,452],{},"    return wrapper\n",[343,454,456],{"class":345,"line":455},19,[343,457,392],{"emptyLinePlaceholder":391},[343,459,461],{"class":345,"line":460},20,[343,462,463],{},"# CPU 密集型任務：計算 Fibonacci 數列\n",[343,465,467],{"class":345,"line":466},21,[343,468,469],{},"def fib(n):\n",[343,471,473],{"class":345,"line":472},22,[343,474,475],{},"    if n \u003C= 1:\n",[343,477,479],{"class":345,"line":478},23,[343,480,481],{},"        return n\n",[343,483,485],{"class":345,"line":484},24,[343,486,487],{},"    return fib(n - 1) + fib(n - 2)\n",[343,489,491],{"class":345,"line":490},25,[343,492,392],{"emptyLinePlaceholder":391},[343,494,496],{"class":345,"line":495},26,[343,497,498],{},"# I/O 綁定任務：模擬網路請求（全局函數）\n",[343,500,502],{"class":345,"line":501},27,[343,503,504],{},"def sync_io_task():\n",[343,506,508],{"class":345,"line":507},28,[343,509,510],{},"    time.sleep(1)  # 模擬 1 秒的 I/O 延遲\n",[343,512,514],{"class":345,"line":513},29,[343,515,392],{"emptyLinePlaceholder":391},[343,517,519],{"class":345,"line":518},30,[343,520,521],{},"# 異步 I/O 任務\n",[343,523,525],{"class":345,"line":524},31,[343,526,527],{},"async def io_bound_task():\n",[343,529,531],{"class":345,"line":530},32,[343,532,533],{},"    await asyncio.sleep(1)  # 模擬 1 秒的 I/O 延遲\n",[343,535,537],{"class":345,"line":536},33,[343,538,539],{},"    return \"I/O 任務完成\"\n",[343,541,543],{"class":345,"line":542},34,[343,544,392],{"emptyLinePlaceholder":391},[343,546,548],{"class":345,"line":547},35,[343,549,550],{},"# 多線程實現\n",[343,552,554],{"class":345,"line":553},36,[343,555,556],{},"@timing_decorator\n",[343,558,560],{"class":345,"line":559},37,[343,561,562],{},"def run_multithreading(n_tasks, task_type=\"cpu\"):\n",[343,564,566],{"class":345,"line":565},38,[343,567,568],{},"    threads = []\n",[343,570,572],{"class":345,"line":571},39,[343,573,574],{},"    if task_type == \"cpu\":\n",[343,576,578],{"class":345,"line":577},40,[343,579,580],{},"        for _ in range(n_tasks):\n",[343,582,584],{"class":345,"line":583},41,[343,585,586],{},"            t = threading.Thread(target=fib, args=(35,))\n",[343,588,590],{"class":345,"line":589},42,[343,591,592],{},"            threads.append(t)\n",[343,594,596],{"class":345,"line":595},43,[343,597,598],{},"            t.start()\n",[343,600,602],{"class":345,"line":601},44,[343,603,604],{},"        for t in threads:\n",[343,606,608],{"class":345,"line":607},45,[343,609,610],{},"            t.join()\n",[343,612,614],{"class":345,"line":613},46,[343,615,616],{},"    else:  # I/O 任務\n",[343,618,620],{"class":345,"line":619},47,[343,621,580],{},[343,623,625],{"class":345,"line":624},48,[343,626,627],{},"            t = threading.Thread(target=sync_io_task)\n",[343,629,631],{"class":345,"line":630},49,[343,632,592],{},[343,634,636],{"class":345,"line":635},50,[343,637,598],{},[343,639,641],{"class":345,"line":640},51,[343,642,604],{},[343,644,646],{"class":345,"line":645},52,[343,647,610],{},[343,649,651],{"class":345,"line":650},53,[343,652,392],{"emptyLinePlaceholder":391},[343,654,656],{"class":345,"line":655},54,[343,657,658],{},"# 多進程實現\n",[343,660,662],{"class":345,"line":661},55,[343,663,556],{},[343,665,667],{"class":345,"line":666},56,[343,668,669],{},"def run_multiprocessing(n_tasks, task_type=\"cpu\"):\n",[343,671,673],{"class":345,"line":672},57,[343,674,675],{},"    processes = []\n",[343,677,679],{"class":345,"line":678},58,[343,680,574],{},[343,682,684],{"class":345,"line":683},59,[343,685,580],{},[343,687,689],{"class":345,"line":688},60,[343,690,691],{},"            p = multiprocessing.Process(target=fib, args=(35,))\n",[343,693,695],{"class":345,"line":694},61,[343,696,697],{},"            processes.append(p)\n",[343,699,701],{"class":345,"line":700},62,[343,702,703],{},"            p.start()\n",[343,705,707],{"class":345,"line":706},63,[343,708,709],{},"        for p in processes:\n",[343,711,713],{"class":345,"line":712},64,[343,714,715],{},"            p.join()\n",[343,717,719],{"class":345,"line":718},65,[343,720,616],{},[343,722,724],{"class":345,"line":723},66,[343,725,580],{},[343,727,729],{"class":345,"line":728},67,[343,730,731],{},"            p = multiprocessing.Process(target=sync_io_task)\n",[343,733,735],{"class":345,"line":734},68,[343,736,697],{},[343,738,740],{"class":345,"line":739},69,[343,741,703],{},[343,743,745],{"class":345,"line":744},70,[343,746,709],{},[343,748,750],{"class":345,"line":749},71,[343,751,715],{},[343,753,755],{"class":345,"line":754},72,[343,756,392],{"emptyLinePlaceholder":391},[343,758,760],{"class":345,"line":759},73,[343,761,762],{},"# Asyncio 實現\n",[343,764,766],{"class":345,"line":765},74,[343,767,556],{},[343,769,771],{"class":345,"line":770},75,[343,772,773],{},"async def run_asyncio(n_tasks):\n",[343,775,777],{"class":345,"line":776},76,[343,778,779],{},"    tasks = [io_bound_task() for _ in range(n_tasks)]\n",[343,781,783],{"class":345,"line":782},77,[343,784,785],{},"    await asyncio.gather(*tasks)\n",[343,787,789],{"class":345,"line":788},78,[343,790,392],{"emptyLinePlaceholder":391},[343,792,794],{"class":345,"line":793},79,[343,795,796],{},"# 禁用 GIL 的多線程實現（需 Python 3.13 --disable-gil）\n",[343,798,800],{"class":345,"line":799},80,[343,801,556],{},[343,803,805],{"class":345,"line":804},81,[343,806,807],{},"def run_nogil_multithreading(n_tasks, task_type=\"cpu\"):\n",[343,809,811],{"class":345,"line":810},82,[343,812,813],{},"    with ThreadPoolExecutor(max_workers=n_tasks) as executor:\n",[343,815,817],{"class":345,"line":816},83,[343,818,819],{},"        if task_type == \"cpu\":\n",[343,821,823],{"class":345,"line":822},84,[343,824,825],{},"            futures = [executor.submit(fib, 35) for _ in range(n_tasks)]\n",[343,827,829],{"class":345,"line":828},85,[343,830,831],{},"        else:  # I/O 任務\n",[343,833,835],{"class":345,"line":834},86,[343,836,837],{},"            futures = [executor.submit(sync_io_task) for _ in range(n_tasks)]\n",[343,839,841],{"class":345,"line":840},87,[343,842,843],{},"        for future in futures:\n",[343,845,847],{"class":345,"line":846},88,[343,848,849],{},"            future.result()\n",[343,851,853],{"class":345,"line":852},89,[343,854,392],{"emptyLinePlaceholder":391},[343,856,858],{"class":345,"line":857},90,[343,859,860],{},"# 主程式\n",[343,862,864],{"class":345,"line":863},91,[343,865,866],{},"if __name__ == \"__main__\":\n",[343,868,870],{"class":345,"line":869},92,[343,871,872],{},"    n_tasks = 4  # 任務數量\n",[343,874,876],{"class":345,"line":875},93,[343,877,392],{"emptyLinePlaceholder":391},[343,879,881],{"class":345,"line":880},94,[343,882,883],{},"    print(\"=== CPU 密集型任務 (計算 Fibonacci 數列) ===\")\n",[343,885,887],{"class":345,"line":886},95,[343,888,889],{},"    print(\"多線程 (Multithreading):\")\n",[343,891,893],{"class":345,"line":892},96,[343,894,895],{},"    run_multithreading(n_tasks, task_type=\"cpu\")\n",[343,897,899],{"class":345,"line":898},97,[343,900,392],{"emptyLinePlaceholder":391},[343,902,904],{"class":345,"line":903},98,[343,905,906],{},"    print(\"\\n多進程 (Multiprocessing):\")\n",[343,908,910],{"class":345,"line":909},99,[343,911,912],{},"    run_multiprocessing(n_tasks, task_type=\"cpu\")\n",[343,914,916],{"class":345,"line":915},100,[343,917,392],{"emptyLinePlaceholder":391},[343,919,921],{"class":345,"line":920},101,[343,922,923],{},"    if sys.version_info >= (3, 13) and hasattr(sys, \"disable_gil\"):\n",[343,925,927],{"class":345,"line":926},102,[343,928,929],{},"        print(\"\\n禁用 GIL 的多線程:\")\n",[343,931,933],{"class":345,"line":932},103,[343,934,935],{},"        run_nogil_multithreading(n_tasks, task_type=\"cpu\")\n",[343,937,939],{"class":345,"line":938},104,[343,940,941],{},"    else:\n",[343,943,945],{"class":345,"line":944},105,[343,946,947],{},"        print(\"\\n禁用 GIL 的多線程: 需要 Python 3.13 並啟用 --disable-gil\")\n",[343,949,951],{"class":345,"line":950},106,[343,952,392],{"emptyLinePlaceholder":391},[343,954,956],{"class":345,"line":955},107,[343,957,958],{},"    print(\"\\n=== I/O 綁定任務 (模擬網路請求) ===\")\n",[343,960,962],{"class":345,"line":961},108,[343,963,889],{},[343,965,967],{"class":345,"line":966},109,[343,968,969],{},"    run_multithreading(n_tasks, task_type=\"io\")\n",[343,971,973],{"class":345,"line":972},110,[343,974,392],{"emptyLinePlaceholder":391},[343,976,978],{"class":345,"line":977},111,[343,979,906],{},[343,981,983],{"class":345,"line":982},112,[343,984,985],{},"    run_multiprocessing(n_tasks, task_type=\"io\")\n",[343,987,989],{"class":345,"line":988},113,[343,990,392],{"emptyLinePlaceholder":391},[343,992,994],{"class":345,"line":993},114,[343,995,996],{},"    print(\"\\nAsyncio:\")\n",[343,998,1000],{"class":345,"line":999},115,[343,1001,1002],{},"    asyncio.run(run_asyncio(n_tasks))\n",[343,1004,1006],{"class":345,"line":1005},116,[343,1007,392],{"emptyLinePlaceholder":391},[343,1009,1011],{"class":345,"line":1010},117,[343,1012,923],{},[343,1014,1016],{"class":345,"line":1015},118,[343,1017,929],{},[343,1019,1021],{"class":345,"line":1020},119,[343,1022,1023],{},"        run_nogil_multithreading(n_tasks, task_type=\"io\")\n",[343,1025,1027],{"class":345,"line":1026},120,[343,1028,941],{},[343,1030,1032],{"class":345,"line":1031},121,[343,1033,947],{},[11,1035,1036],{"id":1036},"執行結果分析",[333,1038,1042],{"className":1039,"code":1040,"language":1041,"meta":338,"style":338},"language-bash shiki shiki-themes github-light github-dark","$ uv run --no-project --python 3.13.5+freethreaded test.py\n\n=== CPU 密集型任務 (計算 Fibonacci 數列) ===\n多線程 (Multithreading):\nrun_multithreading 執行時間: 0.8912 秒\n\n多進程 (Multiprocessing):\nrun_multiprocessing 執行時間: 1.0846 秒\n\n禁用 GIL 的多線程:\nGIL 狀態: 禁用\nrun_nogil_multithreading 執行時間: 0.8713 秒\n\n=== I/O 綁定任務 (模擬網路請求) ===\n多線程 (Multithreading):\nrun_multithreading 執行時間: 1.0072 秒\n\n多進程 (Multiprocessing):\nrun_multiprocessing 執行時間: 1.1046 秒\n\nAsyncio:\nrun_asyncio 執行時間: 0.0000 秒\n\n禁用 GIL 的多線程:\nGIL 狀態: 禁用\nrun_nogil_multithreading 執行時間: 1.0093 秒\n","bash",[340,1043,1044,1070,1074,1098,1106,1120,1124,1132,1144,1148,1159,1170,1182,1186,1199,1205,1216,1220,1226,1237,1241,1246,1258,1262,1270,1278],{"__ignoreMap":338},[343,1045,1046,1050,1054,1057,1061,1064,1067],{"class":345,"line":346},[343,1047,1049],{"class":1048},"sScJk","$",[343,1051,1053],{"class":1052},"sZZnC"," uv",[343,1055,1056],{"class":1052}," run",[343,1058,1060],{"class":1059},"sj4cs"," --no-project",[343,1062,1063],{"class":1059}," --python",[343,1065,1066],{"class":1052}," 3.13.5+freethreaded",[343,1068,1069],{"class":1052}," test.py\n",[343,1071,1072],{"class":345,"line":352},[343,1073,392],{"emptyLinePlaceholder":391},[343,1075,1076,1079,1082,1085,1089,1092,1095],{"class":345,"line":358},[343,1077,1078],{"class":1052},"===",[343,1080,1081],{"class":1052}," CPU",[343,1083,1084],{"class":1052}," 密集型任務",[343,1086,1088],{"class":1087},"sVt8B"," (計算 ",[343,1090,1091],{"class":1052},"Fibonacci",[343,1093,1094],{"class":1052}," 數列",[343,1096,1097],{"class":1087},") ===\n",[343,1099,1100,1103],{"class":345,"line":364},[343,1101,1102],{"class":1048},"多線程",[343,1104,1105],{"class":1087}," (Multithreading):\n",[343,1107,1108,1111,1114,1117],{"class":345,"line":370},[343,1109,1110],{"class":1048},"run_multithreading",[343,1112,1113],{"class":1052}," 執行時間:",[343,1115,1116],{"class":1059}," 0.8912",[343,1118,1119],{"class":1052}," 秒\n",[343,1121,1122],{"class":345,"line":376},[343,1123,392],{"emptyLinePlaceholder":391},[343,1125,1126,1129],{"class":345,"line":382},[343,1127,1128],{"class":1048},"多進程",[343,1130,1131],{"class":1087}," (Multiprocessing):\n",[343,1133,1134,1137,1139,1142],{"class":345,"line":388},[343,1135,1136],{"class":1048},"run_multiprocessing",[343,1138,1113],{"class":1052},[343,1140,1141],{"class":1059}," 1.0846",[343,1143,1119],{"class":1052},[343,1145,1146],{"class":345,"line":395},[343,1147,392],{"emptyLinePlaceholder":391},[343,1149,1150,1153,1156],{"class":345,"line":401},[343,1151,1152],{"class":1048},"禁用",[343,1154,1155],{"class":1052}," GIL",[343,1157,1158],{"class":1052}," 的多線程:\n",[343,1160,1161,1164,1167],{"class":345,"line":407},[343,1162,1163],{"class":1048},"GIL",[343,1165,1166],{"class":1052}," 狀態:",[343,1168,1169],{"class":1052}," 禁用\n",[343,1171,1172,1175,1177,1180],{"class":345,"line":413},[343,1173,1174],{"class":1048},"run_nogil_multithreading",[343,1176,1113],{"class":1052},[343,1178,1179],{"class":1059}," 0.8713",[343,1181,1119],{"class":1052},[343,1183,1184],{"class":345,"line":419},[343,1185,392],{"emptyLinePlaceholder":391},[343,1187,1188,1190,1193,1196],{"class":345,"line":425},[343,1189,1078],{"class":1052},[343,1191,1192],{"class":1052}," I/O",[343,1194,1195],{"class":1052}," 綁定任務",[343,1197,1198],{"class":1087}," (模擬網路請求) ===\n",[343,1200,1201,1203],{"class":345,"line":431},[343,1202,1102],{"class":1048},[343,1204,1105],{"class":1087},[343,1206,1207,1209,1211,1214],{"class":345,"line":437},[343,1208,1110],{"class":1048},[343,1210,1113],{"class":1052},[343,1212,1213],{"class":1059}," 1.0072",[343,1215,1119],{"class":1052},[343,1217,1218],{"class":345,"line":443},[343,1219,392],{"emptyLinePlaceholder":391},[343,1221,1222,1224],{"class":345,"line":449},[343,1223,1128],{"class":1048},[343,1225,1131],{"class":1087},[343,1227,1228,1230,1232,1235],{"class":345,"line":455},[343,1229,1136],{"class":1048},[343,1231,1113],{"class":1052},[343,1233,1234],{"class":1059}," 1.1046",[343,1236,1119],{"class":1052},[343,1238,1239],{"class":345,"line":460},[343,1240,392],{"emptyLinePlaceholder":391},[343,1242,1243],{"class":345,"line":466},[343,1244,1245],{"class":1048},"Asyncio:\n",[343,1247,1248,1251,1253,1256],{"class":345,"line":472},[343,1249,1250],{"class":1048},"run_asyncio",[343,1252,1113],{"class":1052},[343,1254,1255],{"class":1059}," 0.0000",[343,1257,1119],{"class":1052},[343,1259,1260],{"class":345,"line":478},[343,1261,392],{"emptyLinePlaceholder":391},[343,1263,1264,1266,1268],{"class":345,"line":484},[343,1265,1152],{"class":1048},[343,1267,1155],{"class":1052},[343,1269,1158],{"class":1052},[343,1271,1272,1274,1276],{"class":345,"line":490},[343,1273,1163],{"class":1048},[343,1275,1166],{"class":1052},[343,1277,1169],{"class":1052},[343,1279,1280,1282,1284,1287],{"class":345,"line":495},[343,1281,1174],{"class":1048},[343,1283,1113],{"class":1052},[343,1285,1286],{"class":1059}," 1.0093",[343,1288,1119],{"class":1052},[26,1290,1292],{"id":1291},"cpu-密集型任務計算-fibonacci-數列","CPU 密集型任務（計算 Fibonacci 數列）",[16,1294,1295,1296],{},"執行時間排序：",[37,1297,1298],{},"GIL Disabled (0.8713 秒) \u003C 多線程 (0.8912 秒) \u003C 多進程 (1.0846 秒)",[31,1300,1301,1306,1311],{},[34,1302,1303,1305],{},[37,1304,1102],{},"：受 GIL 限制，執行速度與單 CPU 執行差不多",[34,1307,1308,1310],{},[37,1309,1128],{},"：運用多個 CPU 核心加速",[34,1312,1313,1316],{},[37,1314,1315],{},"GIL Disabled","：運用多個 CPU 核心加速，且避免了多進程管理的額外開銷",[26,1318,1320],{"id":1319},"io-綁定任務模擬網路請求","I/O 綁定任務（模擬網路請求）",[16,1322,1295,1323],{},[37,1324,1325],{},"Asyncio (0.0000 秒) \u003C\u003C 多線程 (1.0072 秒) ≈ GIL Disabled (1.0093 秒) ≈ 多進程 (1.1046 秒)",[31,1327,1328,1334],{},[34,1329,1330,1333],{},[37,1331,1332],{},"多線程、多進程、GIL Disabled","：三者效能差不多",[34,1335,1336,1339,1340,1343],{},[37,1337,1338],{},"Asyncio","：效能最好，因為測試任務使用 ",[340,1341,1342],{},"await asyncio.sleep","，基本上不會有運行時間開銷",[11,1345,1346],{"id":1346},"實際應用場景",[16,1348,1349],{},[37,1350,1351],{},"首先需要考慮業務場景，做這個優化加速是否有需要？如果加速沒有很重要的話就是一個 Nice to have 的優化。",[26,1353,1355],{"id":1354},"不建議使用-disabled-gil","不建議使用 Disabled GIL",[16,1357,1358],{},"目前 Disabled GIL 還在測試階段，許多套件都還沒完全支援，也需要自己處理變數管理等問題，目前不考慮使用。",[26,1360,1362],{"id":1361},"適合使用-multiprocessing-的場景","適合使用 Multiprocessing 的場景",[16,1364,1365],{},"非 AI 的大量運算需求，可以使用 Multiprocessing 來加速：",[31,1367,1368,1374],{},[34,1369,1370,1373],{},[37,1371,1372],{},"售電平台的最佳參數求取","：目前策略使用網格搜索，可以切分網格分配給 CPU 執行",[34,1375,1376,1379],{},[37,1377,1378],{},"EV 管理平台","：需要定時計算電站使用率",[26,1381,1383],{"id":1382},"適合使用-asyncio-的場景","適合使用 Asyncio 的場景",[16,1385,1386],{},"網路的大量需求，可以使用 Asyncio 來加速：",[31,1388,1389,1395,1413,1423],{},[34,1390,1391,1394],{},[37,1392,1393],{},"MQTT / OCPP","：這類 pub/sub 協議",[34,1396,1397,134,1400],{},[37,1398,1399],{},"爬蟲或大量 Third-party API 請求",[31,1401,1402],{},[34,1403,1404,1405,1408,1409,1412],{},"使用 ",[340,1406,1407],{},"aiohttp"," 替換 ",[340,1410,1411],{},"requests"," 模組",[34,1414,1415,134,1418],{},[37,1416,1417],{},"Database 異步操作",[31,1419,1420],{},[34,1421,1422],{},"pymongo 遷移到 async client (≥ v4.13)",[34,1424,1425,134,1428],{},[37,1426,1427],{},"Web Framework",[31,1429,1430],{},[34,1431,1432],{},"Flask 遷移到 FastAPI",[11,1434,1435],{"id":1435},"參考資料",[31,1437,1438,1447,1454],{},[34,1439,1440],{},[1441,1442,1446],"a",{"href":1443,"rel":1444},"https://www.youtube.com/watch?v=brYsDi-JajI",[1445],"nofollow","【python】asyncio的理解與入門，搞不明白協程？看這個視頻就夠了",[34,1448,1449],{},[1441,1450,1453],{"href":1451,"rel":1452},"https://www.youtube.com/watch?v=K0BjgYZbgfE&t=94s",[1445],"【python】await機制詳解。再來個硬核內容，把並行和依賴背後的原理全給你講明白",[34,1455,1456],{},[1441,1457,1460],{"href":1458,"rel":1459},"https://pymongo.readthedocs.io/en/stable/async-tutorial.html",[1445],"Async Tutorial - PyMongo 4.14.1 documentation",[1462,1463,1464],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":338,"searchDepth":352,"depth":352,"links":1466},[1467,1468,1474,1475,1476,1480,1485],{"id":13,"depth":352,"text":14},{"id":24,"depth":352,"text":24,"children":1469},[1470,1471,1472,1473],{"id":28,"depth":358,"text":29},{"id":82,"depth":358,"text":83},{"id":121,"depth":358,"text":122},{"id":158,"depth":358,"text":159},{"id":180,"depth":352,"text":180},{"id":331,"depth":352,"text":331},{"id":1036,"depth":352,"text":1036,"children":1477},[1478,1479],{"id":1291,"depth":358,"text":1292},{"id":1319,"depth":358,"text":1320},{"id":1346,"depth":352,"text":1346,"children":1481},[1482,1483,1484],{"id":1354,"depth":358,"text":1355},{"id":1361,"depth":358,"text":1362},{"id":1382,"depth":358,"text":1383},{"id":1435,"depth":352,"text":1435},"技術","2025-10-24","介紹 Python 並發處理方法，包括多線程、多進程、asyncio 以及其實務應用。",false,"md",{},"/blog/concurrency",{"title":5,"description":1488},"blog/concurrency",[1496,1497,1498,1338],"Python","Concurrency","Threading","1DEoTJ_oFj-_n1U7PDKbn7wPsTmsBUK-5vAp6nZ_s0E",[1501,1897,3233],{"id":1502,"title":1503,"author":6,"body":1504,"category":1486,"date":1487,"description":1890,"draft":1489,"extension":1490,"image":338,"meta":1891,"navigation":391,"path":1892,"seo":1893,"stem":1894,"tags":1895,"__hash__":1896},"blog/blog/python-gil-introduction.md","Python GIL 介紹",{"type":8,"value":1505,"toc":1878},[1506,1510,1516,1519,1539,1542,1546,1551,1555,1575,1579,1582,1658,1662,1665,1733,1737,1740,1764,1767,1787,1790,1846,1848],[11,1507,1509],{"id":1508},"什麼是-gil","什麼是 GIL？",[1511,1512,1513],"blockquote",{},[16,1514,1515],{},"💡 GIL 是 CPython 解釋器中的一個鎖，確保在任何時刻只有一個線程可以執行 Python 字節碼。",[16,1517,1518],{},"這意味著即使有多個線程，Python 程式也無法真正利用多核心 CPU 實現並行（parallelism），僅能實現並發（concurrency）的假象。GIL 為 Python 提供了以下好處：",[31,1520,1521,1527,1533],{},[34,1522,1523,1526],{},[37,1524,1525],{},"簡化種族條件（Race Conditions）","：防止Multi thread同時修改共享資料，降低記憶體損壞風險。",[34,1528,1529,1532],{},[37,1530,1531],{},"簡化垃圾回收（Garbage Collection）","：GIL 使記憶體管理更簡單，無需複雜的鎖機制。",[34,1534,1535,1538],{},[37,1536,1537],{},"歷史背景","：Python 於 1991 年設計時，多數電腦僅有單核心 CPU，GIL 是當時簡化實現的合理選擇。",[16,1540,1541],{},"然而，GIL 的缺點顯而易見：它限制了多核心 CPU 的利用率，使 Python 在 CPU 密集型任務（如數學計算、資料解析、AI 模型訓練）上的效能不如預期。",[11,1543,1545],{"id":1544},"為什麼移除-gilpep-703-python-313","為什麼移除 GIL？（PEP 703, Python 3.13）",[1511,1547,1548],{},[16,1549,1550],{},"💡 隨著硬體技術的進步（多核心 CPU 普及）和 Python 社群對高效能的需求，GIL 逐漸成為瓶頸。PEP 703（Python 3.13）提出使 GIL 可選，允許程式在編譯時或運行時禁用 GIL，讓Multi thread直接利用操作系統的線程調度器，實現真正的並行。",[26,1552,1554],{"id":1553},"移除-gil-的動機","移除 GIL 的動機",[31,1556,1557,1563,1569],{},[34,1558,1559,1562],{},[37,1560,1561],{},"社群需求","：開發者希望 Python 能更快，並充分利用現代多核心硬體。",[34,1564,1565,1568],{},[37,1566,1567],{},"效能提升","：禁用 GIL 後，某些 CPU 密集型任務（如桶排序、分形生成）可顯著加速。",[34,1570,1571,1574],{},[37,1572,1573],{},"競爭壓力","：其他語言（如 Go、Rust）支援真正的並行，Python 需跟上時代。",[26,1576,1578],{"id":1577},"移除-gil-的挑戰","移除 GIL 的挑戰",[16,1580,1581],{},"移除 GIL 並非易事，因為 Python 的許多核心機制依賴 GIL 的保護。以下是主要挑戰：",[1583,1584,1585,1617,1629,1646],"ol",{},[34,1586,1587,1590],{},[37,1588,1589],{},"引用計數（Reference Counting）",[31,1591,1592,1598],{},[34,1593,1594,1597],{},[37,1595,1596],{},"問題","：傳統的非原子引用計數（non-atomic reference counting）不具線程安全，可能導致種族條件。例如，refcount++ 分三步（讀、加、寫），可能被其他線程中斷。",[34,1599,1600,134,1603],{},[37,1601,1602],{},"解決方案",[31,1604,1605,1611],{},[34,1606,1607,1610],{},[37,1608,1609],{},"原子引用計數","：線程安全，但速度慢 10x-100x。",[34,1612,1613,1616],{},[37,1614,1615],{},"偏向引用計數（Biased Reference Counting）","：檢查引用是否僅屬於單一線程，若是則使用快速的非原子計數，否則使用原子計數。",[34,1618,1619,1622],{},[37,1620,1621],{},"垃圾回收（Garbage Collection）",[31,1623,1624],{},[34,1625,1626,1628],{},[37,1627,1596],{},"：傳統垃圾回收依賴 GIL 保護，需改用延遲引用計數（deferred reference counting）來處理循環引用。",[34,1630,1631,1634],{},[37,1632,1633],{},"記憶體分配",[31,1635,1636,1641],{},[34,1637,1638,1640],{},[37,1639,1596],{},"：現有記憶體分配器假設 GIL 保護，不具線程安全。",[34,1642,1643,1645],{},[37,1644,1602],{},"：開發新的線程安全記憶體分配器，優化列表和字典的快速讀取。",[34,1647,1648,1651],{},[37,1649,1650],{},"兼容性",[31,1652,1653],{},[34,1654,1655,1657],{},[37,1656,1596],{},"：許多 C-API 擴展（如 NumPy、Pandas）假設 GIL 存在，需重新編譯以支援無 GIL 環境。",[11,1659,1661],{"id":1660},"gil-與禁用-gil-的比較","GIL 與禁用 GIL 的比較",[16,1663,1664],{},"以下表格比較了 GIL 和禁用 GIL 的特點：",[182,1666,1667,1679],{},[185,1668,1669],{},[188,1670,1671,1673,1676],{},[191,1672,193],{},[191,1674,1675],{},"啟用 GIL",[191,1677,1678],{},"禁用 GIL",[207,1680,1681,1694,1707,1720],{},[188,1682,1683,1688,1691],{},[212,1684,1685],{},[37,1686,1687],{},"並行性",[212,1689,1690],{},"非真正的並行，僅一個線程執行",[212,1692,1693],{},"真正的Multi thread並行，利用多核心",[188,1695,1696,1701,1704],{},[212,1697,1698],{},[37,1699,1700],{},"資料共享",[212,1702,1703],{},"簡單，無需額外鎖",[212,1705,1706],{},"需小心處理種族條件（如使用 threading.Lock）",[188,1708,1709,1714,1717],{},[212,1710,1711],{},[37,1712,1713],{},"效能",[212,1715,1716],{},"受限於 GIL，CPU 綁定任務慢",[212,1718,1719],{},"視程式而定，可能顯著提升（如桶排序、分形生成）",[188,1721,1722,1727,1730],{},[212,1723,1724],{},[37,1725,1726],{},"擴展相容性",[212,1728,1729],{},"廣泛支援",[212,1731,1732],{},"需檢查擴展版本（如 Cython、NumPy）",[11,1734,1736],{"id":1735},"禁用-gil-的推薦場景","禁用 GIL 的推薦場景",[16,1738,1739],{},"禁用 GIL 在以下場景中特別有用：",[31,1741,1742,1748,1753,1758],{},[34,1743,1744,1747],{},[37,1745,1746],{},"ETL 處理","：資料提取、轉換和載入需要大量計算。",[34,1749,1750,1752],{},[37,1751,105],{},"：如濾波、轉換等 CPU 密集型操作。",[34,1754,1755,1757],{},[37,1756,108],{},"：處理大量資料並進行模式匹配。",[34,1759,1760,1763],{},[37,1761,1762],{},"即時分析","：需要快速響應的計算任務。",[11,1765,1766],{"id":1766},"注意事項",[31,1768,1769,1775,1781],{},[34,1770,1771,1774],{},[37,1772,1773],{},"Race Condition","：禁用 GIL 後，Multi thread可能同時存取共享資料，導致記憶體損壞。建議使用 threading.Lock 或其他同步機制。",[34,1776,1777,1780],{},[37,1778,1779],{},"擴展模組相容性","：確認使用的庫（如 NumPy、Pandas、PyTorch）支援無 GIL 環境。",[34,1782,1783,1786],{},[37,1784,1785],{},"效能不確定性","：禁用 GIL 不保證所有程式都變快，需針對具體任務進行基準測試。",[11,1788,1789],{"id":1789},"常見誤解與解答",[1583,1791,1792,1806,1816,1826,1836],{},[34,1793,1794,1797,1798],{},[37,1795,1796],{},"並發(Concurrency)與並行(Parallelism)的區別","：",[31,1799,1800,1803],{},[34,1801,1802],{},"並發是多任務「看似」同時進行，實際上可能是切換執行。",[34,1804,1805],{},"並行是多任務在多核心上真正同時執行。",[34,1807,1808,1811],{},[37,1809,1810],{},"為什麼 Python 慢？",[31,1812,1813],{},[34,1814,1815],{},"GIL 限制了Multi thread的並行性，導致 CPU 綁定任務無法充分利用多核心。",[34,1817,1818,1821],{},[37,1819,1820],{},"禁用 GIL 如何管理 CPU 使用？",[31,1822,1823],{},[34,1824,1825],{},"透過操作系統的線程調度器，讓Multi thread在多核心上並行執行。",[34,1827,1828,1831],{},[37,1829,1830],{},"禁用 GIL 是否總是更快？",[31,1832,1833],{},[34,1834,1835],{},"不一定，效能提升取決於任務類型和程式設計。某些任務（如 I/O 綁定）可能無明顯改善。",[34,1837,1838,1841],{},[37,1839,1840],{},"簡單資料共享是什麼？",[31,1842,1843],{},[34,1844,1845],{},"指線程間直接存取記憶體的能力，而Multi process需要管道或共享記憶體，較為複雜。",[11,1847,1435],{"id":1435},[31,1849,1850,1857,1864,1871],{},[34,1851,1852],{},[1441,1853,1856],{"href":1854,"rel":1855},"https://peps.python.org/pep-0703/",[1445],"PEP 703 – Making the Global Interpreter Lock Optional in CPython",[34,1858,1859],{},[1441,1860,1863],{"href":1861,"rel":1862},"https://tw.pycon.org/2025/zh-hant/conference/keynotes#Donghee_Na",[1445],"PyCon TW 2025 主題演講",[34,1865,1866],{},[1441,1867,1870],{"href":1868,"rel":1869},"https://tw.pycon.org/2025/zh-hant/conference/talk/352",[1445],"PyCon TW 2025 - Talk 352",[34,1872,1873],{},[1441,1874,1877],{"href":1875,"rel":1876},"https://tw.pycon.org/2025/zh-hant/conference/talk/335",[1445],"PyCon TW 2025 - Talk 335",{"title":338,"searchDepth":352,"depth":352,"links":1879},[1880,1881,1885,1886,1887,1888,1889],{"id":1508,"depth":352,"text":1509},{"id":1544,"depth":352,"text":1545,"children":1882},[1883,1884],{"id":1553,"depth":358,"text":1554},{"id":1577,"depth":358,"text":1578},{"id":1660,"depth":352,"text":1661},{"id":1735,"depth":352,"text":1736},{"id":1766,"depth":352,"text":1766},{"id":1789,"depth":352,"text":1789},{"id":1435,"depth":352,"text":1435},"Python 全域直譯器鎖（GIL）介紹",{},"/blog/python-gil-introduction",{"title":1503,"description":1890},"blog/python-gil-introduction",[1496,1163,1498,1497],"HEyyMIX4ERxJ70UGu2zKfrzTPLf2cYOCJfUly7xneRo",{"id":1898,"title":1899,"author":6,"body":1900,"category":1486,"date":1487,"description":3223,"draft":1489,"extension":1490,"image":338,"meta":3224,"navigation":391,"path":3225,"seo":3226,"stem":3227,"tags":3228,"__hash__":3232},"blog/blog/uv-and-ruff.md","uv and Ruff: 最佳的 Python 工具鏈",{"type":8,"value":1901,"toc":3205},[1902,1905,1910,1913,1921,1925,1955,1959,1966,1988,1995,2006,2010,2238,2241,2246,2254,2259,2265,2268,2271,2279,2283,2305,2307,2311,2358,2360,2363,2366,2391,2394,2398,2436,2440,2541,2545,2620,2627,2634,2912,2916,2936,2940,2951,2955,3014,3018,3043,3047,3175,3177,3179,3202],[1903,1904,14],"h1",{"id":13},[1511,1906,1907],{},[16,1908,1909],{},"若遇過 format on save 很慢 / 裝環境很麻煩 / 套件衝突，那這篇文章可能對你有用",[1903,1911,1912],{"id":1912},"uv",[1511,1914,1915,1918],{},[16,1916,1917],{},"uv 是一個極速的 Python 套件管理器和專案管理工具，被設計為 pip、pip-tools、pipx、poetry、pyenv、virtualenv 等工具的統一替代方案。",[16,1919,1920],{},"uv 的核心理念是提供一個快速、可靠且易用的 Python 工具鏈。",[11,1922,1924],{"id":1923},"uv-的主要特點","uv 的主要特點",[31,1926,1927,1932,1945,1952],{},[34,1928,1929],{},[37,1930,1931],{},"速度極快",[34,1933,1934,1937],{},[37,1935,1936],{},"統一的工作流程",[31,1938,1939,1942],{},[34,1940,1941],{},"整合了套件安裝、虛擬環境管理、專案初始化等功能",[34,1943,1944],{},"一個工具解決多個需求，簡化開發工作流程",[34,1946,1947,1948,1951],{},"確保所有環境部署時依賴相同 (",[340,1949,1950],{},"uv.lock",")",[34,1953,1954],{},"方便切換 python 版本，以及在設定檔中鎖定 python 版本",[11,1956,1958],{"id":1957},"requirementstxt-vs-pyprojecttoml","requirements.txt vs pyproject.toml",[1511,1960,1961],{},[16,1962,1963],{},[37,1964,1965],{},"pyproject.toml 是官方標準",[31,1967,1968,1971,1974,1977,1980],{},[34,1969,1970],{},"PEP 518、PEP 621 等官方標準定義的專案配置格式",[34,1972,1973],{},"Python 生態系統的統一標準，未來發展方向",[34,1975,1976],{},"被所有現代工具（pip、uv、poetry、setuptools）支援",[34,1978,1979],{},"可以根據開發、打包、部署等環境分別配置",[34,1981,1982,1983],{},"有 support 的工具都可以統一配置\n",[31,1984,1985],{},[34,1986,1987],{},"例如等等要介紹的 ruff",[1511,1989,1990],{},[16,1991,1992],{},[37,1993,1994],{},"requirements.txt 是非正式慣例",[31,1996,1997,2000,2003],{},[34,1998,1999],{},"只是社群約定俗成的做法",[34,2001,2002],{},"沒有正式規範，格式相對簡陋",[34,2004,2005],{},"逐漸被新工具取代",[26,2007,2009],{"id":2008},"範例-pyprojecttoml","範例 pyproject.toml",[333,2011,2015],{"className":2012,"code":2013,"language":2014,"meta":338,"style":338},"language-toml shiki shiki-themes github-light github-dark","[project]\nname = \"my-awesome-project\"\nversion = \"1.0.0\"\ndescription = \"A fantastic Python project\"\nauthors = [{name = \"Your Name\", email = \"you@example.com\"}]\nlicense = {text = \"MIT\"}\nreadme = \"README.md\"\nkeywords = [\"python\", \"awesome\"]\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Programming Language :: Python :: 3.11\",\n]\n\n# 依賴管理\ndependencies = [\n    \"requests>=2.28.0\",\n    \"click>=8.0.0\",\n]\n\n# 開發依賴\n[project.optional-dependencies]\ndev = [\n    \"pytest>=7.0.0\",\n    \"ruff>=0.1.0\",\n    \"mypy>=1.0.0\",\n]\ntest = [\n    \"pytest-cov>=4.0.0\",\n    \"pytest-mock>=3.10.0\",\n]\n\n# 工具配置\n[tool.ruff]\nline-length = 88\ntarget-version = \"py311\"\n\n[tool.ruff.lint]\nselect = [\"E\", \"F\", \"UP\", \"B\", \"SIM\", \"I\"]\n\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\npython_files = [\"test_*.py\"]\n\n[build-system]\nrequires = [\"setuptools>=61.0\"]\nbuild-backend = \"setuptools.build_meta\"\n","toml",[340,2016,2017,2022,2027,2032,2037,2042,2047,2052,2057,2062,2067,2072,2077,2081,2086,2091,2096,2101,2105,2109,2114,2119,2124,2129,2134,2139,2143,2148,2153,2158,2162,2166,2171,2176,2181,2186,2190,2195,2200,2204,2209,2214,2219,2223,2228,2233],{"__ignoreMap":338},[343,2018,2019],{"class":345,"line":346},[343,2020,2021],{},"[project]\n",[343,2023,2024],{"class":345,"line":352},[343,2025,2026],{},"name = \"my-awesome-project\"\n",[343,2028,2029],{"class":345,"line":358},[343,2030,2031],{},"version = \"1.0.0\"\n",[343,2033,2034],{"class":345,"line":364},[343,2035,2036],{},"description = \"A fantastic Python project\"\n",[343,2038,2039],{"class":345,"line":370},[343,2040,2041],{},"authors = [{name = \"Your Name\", email = \"you@example.com\"}]\n",[343,2043,2044],{"class":345,"line":376},[343,2045,2046],{},"license = {text = \"MIT\"}\n",[343,2048,2049],{"class":345,"line":382},[343,2050,2051],{},"readme = \"README.md\"\n",[343,2053,2054],{"class":345,"line":388},[343,2055,2056],{},"keywords = [\"python\", \"awesome\"]\n",[343,2058,2059],{"class":345,"line":395},[343,2060,2061],{},"classifiers = [\n",[343,2063,2064],{"class":345,"line":401},[343,2065,2066],{},"    \"Development Status :: 4 - Beta\",\n",[343,2068,2069],{"class":345,"line":407},[343,2070,2071],{},"    \"Programming Language :: Python :: 3.11\",\n",[343,2073,2074],{"class":345,"line":413},[343,2075,2076],{},"]\n",[343,2078,2079],{"class":345,"line":419},[343,2080,392],{"emptyLinePlaceholder":391},[343,2082,2083],{"class":345,"line":425},[343,2084,2085],{},"# 依賴管理\n",[343,2087,2088],{"class":345,"line":431},[343,2089,2090],{},"dependencies = [\n",[343,2092,2093],{"class":345,"line":437},[343,2094,2095],{},"    \"requests>=2.28.0\",\n",[343,2097,2098],{"class":345,"line":443},[343,2099,2100],{},"    \"click>=8.0.0\",\n",[343,2102,2103],{"class":345,"line":449},[343,2104,2076],{},[343,2106,2107],{"class":345,"line":455},[343,2108,392],{"emptyLinePlaceholder":391},[343,2110,2111],{"class":345,"line":460},[343,2112,2113],{},"# 開發依賴\n",[343,2115,2116],{"class":345,"line":466},[343,2117,2118],{},"[project.optional-dependencies]\n",[343,2120,2121],{"class":345,"line":472},[343,2122,2123],{},"dev = [\n",[343,2125,2126],{"class":345,"line":478},[343,2127,2128],{},"    \"pytest>=7.0.0\",\n",[343,2130,2131],{"class":345,"line":484},[343,2132,2133],{},"    \"ruff>=0.1.0\",\n",[343,2135,2136],{"class":345,"line":490},[343,2137,2138],{},"    \"mypy>=1.0.0\",\n",[343,2140,2141],{"class":345,"line":495},[343,2142,2076],{},[343,2144,2145],{"class":345,"line":501},[343,2146,2147],{},"test = [\n",[343,2149,2150],{"class":345,"line":507},[343,2151,2152],{},"    \"pytest-cov>=4.0.0\",\n",[343,2154,2155],{"class":345,"line":513},[343,2156,2157],{},"    \"pytest-mock>=3.10.0\",\n",[343,2159,2160],{"class":345,"line":518},[343,2161,2076],{},[343,2163,2164],{"class":345,"line":524},[343,2165,392],{"emptyLinePlaceholder":391},[343,2167,2168],{"class":345,"line":530},[343,2169,2170],{},"# 工具配置\n",[343,2172,2173],{"class":345,"line":536},[343,2174,2175],{},"[tool.ruff]\n",[343,2177,2178],{"class":345,"line":542},[343,2179,2180],{},"line-length = 88\n",[343,2182,2183],{"class":345,"line":547},[343,2184,2185],{},"target-version = \"py311\"\n",[343,2187,2188],{"class":345,"line":553},[343,2189,392],{"emptyLinePlaceholder":391},[343,2191,2192],{"class":345,"line":559},[343,2193,2194],{},"[tool.ruff.lint]\n",[343,2196,2197],{"class":345,"line":565},[343,2198,2199],{},"select = [\"E\", \"F\", \"UP\", \"B\", \"SIM\", \"I\"]\n",[343,2201,2202],{"class":345,"line":571},[343,2203,392],{"emptyLinePlaceholder":391},[343,2205,2206],{"class":345,"line":577},[343,2207,2208],{},"[tool.pytest.ini_options]\n",[343,2210,2211],{"class":345,"line":583},[343,2212,2213],{},"testpaths = [\"tests\"]\n",[343,2215,2216],{"class":345,"line":589},[343,2217,2218],{},"python_files = [\"test_*.py\"]\n",[343,2220,2221],{"class":345,"line":595},[343,2222,392],{"emptyLinePlaceholder":391},[343,2224,2225],{"class":345,"line":601},[343,2226,2227],{},"[build-system]\n",[343,2229,2230],{"class":345,"line":607},[343,2231,2232],{},"requires = [\"setuptools>=61.0\"]\n",[343,2234,2235],{"class":345,"line":613},[343,2236,2237],{},"build-backend = \"setuptools.build_meta\"\n",[26,2239,2240],{"id":2240},"專案檔案架構比較",[16,2242,2243],{},[37,2244,2245],{},"使用 requirements.txt 的專案結構",[333,2247,2252],{"className":2248,"code":2250,"language":2251},[2249],"language-text","my-project/\n├── requirements.txt\n├── requirements-dev.txt\n├── requirements-test.txt\n├── setup.py        # setuptools 配置\n├── setup.cfg\n├── pytest.ini      # pytest 配置\n├── mypy.ini        # mypy 配置\n├── .coveragerc     # coverage 配置\n└── pyproject.toml  # 只給 ruff 用\n","text",[340,2253,2250],{"__ignoreMap":338},[16,2255,2256],{},[37,2257,2258],{},"使用 pyproject.toml 的專案結構",[333,2260,2263],{"className":2261,"code":2262,"language":2251},[2249],"my-project/\n├── pyproject.toml  # 所有配置都在這裡\n├── uv.lock         # 版本鎖定檔案\n└── src/\n",[340,2264,2262],{"__ignoreMap":338},[2266,2267],"hr",{},[1903,2269,2270],{"id":2270},"ruff",[1511,2272,2273,2276],{},[16,2274,2275],{},"ruff 是一個極速的 Python linter 和 code formatter，用 Rust 編寫，可以替代 Flake8、isort、Black 等多個工具。",[16,2277,2278],{},"它的目標是提供最快速、最全面的 Python 程式碼品質檢查和格式化解決方案。",[11,2280,2282],{"id":2281},"ruff-的主要特點","ruff 的主要特點",[31,2284,2285,2287,2300],{},[34,2286,1931],{},[34,2288,2289,2292],{},[37,2290,2291],{},"豐富的規則集",[31,2293,2294,2297],{},[34,2295,2296],{},"支援 800+ 個 lint 規則",[34,2298,2299],{},"整合了 Flake8、isort、pycodestyle、pyflakes 等工具的規則",[34,2301,2302],{},[37,2303,2304],{},"無需配置即可使用",[2266,2306],{},[1903,2308,2310],{"id":2309},"為什麼要使用-uv-和-ruff","為什麼要使用 uv 和 ruff？",[31,2312,2313,2329,2335,2352],{},[34,2314,2315,2318,2319],{},[37,2316,2317],{},"即時程式碼檢查","：ruff 的極速檢查讓程式碼品質控制變得無縫\n",[31,2320,2321],{},[34,2322,2323,2324],{},"尤其公司專案目前很多軟體動輒幾千行（較少抽象與依功能分割檔案），使用傳統程式碼檢查與 format 工具需要接近 1 分鐘或甚至更多\n",[31,2325,2326],{},[34,2327,2328],{},"想像你每次 Ctrl + S 都要等 1 分鐘…",[34,2330,2331,2334],{},[37,2332,2333],{},"穩定的工具鏈","：目前 uv 與 ruff 算是在各自領域（套件管理與 lint check / format）統一江湖的存在，未來不太會遇到需要再更換的情況",[34,2336,2337,2338],{},"為了未來可能需要統一產品版本鋪路，使用 Git 協作的情況會越來越多\n",[31,2339,2340,2346],{},[34,2341,2342,2345],{},[37,2343,2344],{},"一致的環境","：uv 確保所有團隊成員使用相同的依賴版本",[34,2347,2348,2351],{},[37,2349,2350],{},"統一的程式碼風格","：ruff 自動化程式碼風格檢查和格式化",[34,2353,2354,2357],{},[37,2355,2356],{},"更快的 CI/CD","：極速的執行效能減少構建時間，現在也有使用 GKE 的專案，可以減少 runner 開銷",[2266,2359],{},[1903,2361,2362],{"id":2362},"遷移範例",[16,2364,2365],{},"假設原本使用 requirements.txt + 很多 lint error",[31,2367,2368],{},[34,2369,2370,2371],{},"lint error 例如：\n",[31,2372,2373,2378,2381,2384],{},[34,2374,2375],{},[340,2376,2377],{},"if a == None",[34,2379,2380],{},"assign var or import but never used",[34,2382,2383],{},"assign a python keyword as a var name",[34,2385,2386,2387,2390],{},"use ",[340,2388,2389],{},"format"," instead of f-string",[16,2392,2393],{},"有遇到任何問題找 AI 處理，或是看文檔的 best practices",[11,2395,2397],{"id":2396},"_1-安裝與驗證","1. 安裝與驗證",[333,2399,2401],{"className":1039,"code":2400,"language":1041,"meta":338,"style":338},"# 安裝 uv\npip install uv\n\n# 驗證（否則要看一下文檔，根據作業系統有不同安裝方式）\nuv --version\n",[340,2402,2403,2409,2420,2424,2429],{"__ignoreMap":338},[343,2404,2405],{"class":345,"line":346},[343,2406,2408],{"class":2407},"sJ8bj","# 安裝 uv\n",[343,2410,2411,2414,2417],{"class":345,"line":352},[343,2412,2413],{"class":1048},"pip",[343,2415,2416],{"class":1052}," install",[343,2418,2419],{"class":1052}," uv\n",[343,2421,2422],{"class":345,"line":358},[343,2423,392],{"emptyLinePlaceholder":391},[343,2425,2426],{"class":345,"line":364},[343,2427,2428],{"class":2407},"# 驗證（否則要看一下文檔，根據作業系統有不同安裝方式）\n",[343,2430,2431,2433],{"class":345,"line":370},[343,2432,1912],{"class":1048},[343,2434,2435],{"class":1059}," --version\n",[11,2437,2439],{"id":2438},"_2-init-專案與安裝依賴","2. init 專案與安裝依賴",[333,2441,2443],{"className":1039,"code":2442,"language":1041,"meta":338,"style":338},"# 移到專案根目錄\ncd {your_project}\n\n# init 專案\nuv init --no-readme\n\n# 從現有 requirements.txt 遷移\nuv add -r requirements.txt\n\n# 手動新增依賴 (if needed)\nuv add requests numpy\n\n# 安裝 ruff\nuv add --dev ruff\n",[340,2444,2445,2450,2458,2462,2467,2477,2481,2486,2499,2503,2508,2520,2524,2529],{"__ignoreMap":338},[343,2446,2447],{"class":345,"line":346},[343,2448,2449],{"class":2407},"# 移到專案根目錄\n",[343,2451,2452,2455],{"class":345,"line":352},[343,2453,2454],{"class":1059},"cd",[343,2456,2457],{"class":1052}," {your_project}\n",[343,2459,2460],{"class":345,"line":358},[343,2461,392],{"emptyLinePlaceholder":391},[343,2463,2464],{"class":345,"line":364},[343,2465,2466],{"class":2407},"# init 專案\n",[343,2468,2469,2471,2474],{"class":345,"line":370},[343,2470,1912],{"class":1048},[343,2472,2473],{"class":1052}," init",[343,2475,2476],{"class":1059}," --no-readme\n",[343,2478,2479],{"class":345,"line":376},[343,2480,392],{"emptyLinePlaceholder":391},[343,2482,2483],{"class":345,"line":382},[343,2484,2485],{"class":2407},"# 從現有 requirements.txt 遷移\n",[343,2487,2488,2490,2493,2496],{"class":345,"line":388},[343,2489,1912],{"class":1048},[343,2491,2492],{"class":1052}," add",[343,2494,2495],{"class":1059}," -r",[343,2497,2498],{"class":1052}," requirements.txt\n",[343,2500,2501],{"class":345,"line":395},[343,2502,392],{"emptyLinePlaceholder":391},[343,2504,2505],{"class":345,"line":401},[343,2506,2507],{"class":2407},"# 手動新增依賴 (if needed)\n",[343,2509,2510,2512,2514,2517],{"class":345,"line":407},[343,2511,1912],{"class":1048},[343,2513,2492],{"class":1052},[343,2515,2516],{"class":1052}," requests",[343,2518,2519],{"class":1052}," numpy\n",[343,2521,2522],{"class":345,"line":413},[343,2523,392],{"emptyLinePlaceholder":391},[343,2525,2526],{"class":345,"line":419},[343,2527,2528],{"class":2407},"# 安裝 ruff\n",[343,2530,2531,2533,2535,2538],{"class":345,"line":425},[343,2532,1912],{"class":1048},[343,2534,2492],{"class":1052},[343,2536,2537],{"class":1059}," --dev",[343,2539,2540],{"class":1052}," ruff\n",[11,2542,2544],{"id":2543},"_3-設定-python-版本","3. 設定 python 版本",[333,2546,2548],{"className":1039,"code":2547,"language":1041,"meta":338,"style":338},"# 查看可用的 Python 版本\nuv python list\n\n# 範例輸出：\n# cpython-3.14.0b4-macos-aarch64-none                 \u003Cdownload available>\n# cpython-3.14.0b4+freethreaded-macos-aarch64-none    \u003Cdownload available>\n# cpython-3.13.5-macos-aarch64-none                   /opt/homebrew/bin/python3.13\n# cpython-3.13.5-macos-aarch64-none                   /opt/homebrew/bin/python3\n# cpython-3.13.5-macos-aarch64-none                   \u003Cdownload available>\n\n# 設定專案使用的 Python 版本（例如 3.10）\nuv python pin 3.10\n",[340,2549,2550,2555,2565,2569,2574,2579,2584,2589,2594,2599,2603,2608],{"__ignoreMap":338},[343,2551,2552],{"class":345,"line":346},[343,2553,2554],{"class":2407},"# 查看可用的 Python 版本\n",[343,2556,2557,2559,2562],{"class":345,"line":352},[343,2558,1912],{"class":1048},[343,2560,2561],{"class":1052}," python",[343,2563,2564],{"class":1052}," list\n",[343,2566,2567],{"class":345,"line":358},[343,2568,392],{"emptyLinePlaceholder":391},[343,2570,2571],{"class":345,"line":364},[343,2572,2573],{"class":2407},"# 範例輸出：\n",[343,2575,2576],{"class":345,"line":370},[343,2577,2578],{"class":2407},"# cpython-3.14.0b4-macos-aarch64-none                 \u003Cdownload available>\n",[343,2580,2581],{"class":345,"line":376},[343,2582,2583],{"class":2407},"# cpython-3.14.0b4+freethreaded-macos-aarch64-none    \u003Cdownload available>\n",[343,2585,2586],{"class":345,"line":382},[343,2587,2588],{"class":2407},"# cpython-3.13.5-macos-aarch64-none                   /opt/homebrew/bin/python3.13\n",[343,2590,2591],{"class":345,"line":388},[343,2592,2593],{"class":2407},"# cpython-3.13.5-macos-aarch64-none                   /opt/homebrew/bin/python3\n",[343,2595,2596],{"class":345,"line":395},[343,2597,2598],{"class":2407},"# cpython-3.13.5-macos-aarch64-none                   \u003Cdownload available>\n",[343,2600,2601],{"class":345,"line":401},[343,2602,392],{"emptyLinePlaceholder":391},[343,2604,2605],{"class":345,"line":407},[343,2606,2607],{"class":2407},"# 設定專案使用的 Python 版本（例如 3.10）\n",[343,2609,2610,2612,2614,2617],{"class":345,"line":413},[343,2611,1912],{"class":1048},[343,2613,2561],{"class":1052},[343,2615,2616],{"class":1052}," pin",[343,2618,2619],{"class":1059}," 3.10\n",[11,2621,2623,2624],{"id":2622},"_4-設定-pyprojecttoml","4. 設定 ",[340,2625,2626],{},"pyproject.toml",[16,2628,2629,2630,2633],{},"下面只是範例，基本上只需要注意 ",[340,2631,2632],{},"tool.ruff"," 相關的部分",[333,2635,2637],{"className":2012,"code":2636,"language":2014,"meta":338,"style":338},"[project]\nname = \"my-project\"\nversion = \"0.1.0\"\ndescription = \"Add your description here\"\ndependencies = [\n    \"numpy>=1.24.0\",\n    \"requests>=2.31.0\",\n]\n\n[tool.uv]\ndev-dependencies = [\n    \"black>=23.7.0\",\n    \"flake8>=6.0.0\",\n    \"pytest>=7.4.0\",\n    \"ruff>=0.1.6\",\n]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n# 加上 ruff 相關設置\n[tool.ruff]\nline-length = 88\ntarget-version = \"py310\"  # 改成你的 python 版本\n\n[tool.ruff.lint]\nselect = [\n    \"E\",   # pycodestyle errors\n    \"W\",   # pycodestyle warnings\n    \"F\",   # pyflakes\n    \"I\",   # isort\n    \"C\",   # flake8-comprehensions\n    \"B\",   # flake8-bugbear\n    \"UP\",  # pyupgrade\n]\nignore = [\n    \"E501\",  # line too long, handled by black\n    \"B008\",  # do not perform function calls in argument defaults\n    \"C901\",  # too complex\n    \"W191\",  # indentation contains tabs\n]\nexclude = [\n    \"tests/*\",\n    \"venv/*\",\n    \"*/site-packages/*\",\n    \"*/static/*\",\n    \"wsgi.py\",\n]\nfixable = [\"ALL\"]\nunfixable = []\n\n[tool.ruff.lint.per-file-ignores]\n\"tests/**/*\" = [\"S101\"]  # 測試中允許 assert\n\n[tool.ruff.format]\nquote-style = \"double\"\nindent-style = \"space\"\n",[340,2638,2639,2643,2648,2653,2658,2662,2667,2672,2676,2680,2685,2690,2695,2700,2705,2710,2714,2718,2722,2727,2732,2736,2741,2745,2749,2754,2758,2762,2767,2772,2777,2782,2787,2792,2797,2802,2806,2811,2816,2821,2826,2831,2835,2840,2845,2850,2855,2860,2865,2869,2874,2879,2883,2888,2893,2897,2902,2907],{"__ignoreMap":338},[343,2640,2641],{"class":345,"line":346},[343,2642,2021],{},[343,2644,2645],{"class":345,"line":352},[343,2646,2647],{},"name = \"my-project\"\n",[343,2649,2650],{"class":345,"line":358},[343,2651,2652],{},"version = \"0.1.0\"\n",[343,2654,2655],{"class":345,"line":364},[343,2656,2657],{},"description = \"Add your description here\"\n",[343,2659,2660],{"class":345,"line":370},[343,2661,2090],{},[343,2663,2664],{"class":345,"line":376},[343,2665,2666],{},"    \"numpy>=1.24.0\",\n",[343,2668,2669],{"class":345,"line":382},[343,2670,2671],{},"    \"requests>=2.31.0\",\n",[343,2673,2674],{"class":345,"line":388},[343,2675,2076],{},[343,2677,2678],{"class":345,"line":395},[343,2679,392],{"emptyLinePlaceholder":391},[343,2681,2682],{"class":345,"line":401},[343,2683,2684],{},"[tool.uv]\n",[343,2686,2687],{"class":345,"line":407},[343,2688,2689],{},"dev-dependencies = [\n",[343,2691,2692],{"class":345,"line":413},[343,2693,2694],{},"    \"black>=23.7.0\",\n",[343,2696,2697],{"class":345,"line":419},[343,2698,2699],{},"    \"flake8>=6.0.0\",\n",[343,2701,2702],{"class":345,"line":425},[343,2703,2704],{},"    \"pytest>=7.4.0\",\n",[343,2706,2707],{"class":345,"line":431},[343,2708,2709],{},"    \"ruff>=0.1.6\",\n",[343,2711,2712],{"class":345,"line":437},[343,2713,2076],{},[343,2715,2716],{"class":345,"line":443},[343,2717,392],{"emptyLinePlaceholder":391},[343,2719,2720],{"class":345,"line":449},[343,2721,2227],{},[343,2723,2724],{"class":345,"line":455},[343,2725,2726],{},"requires = [\"hatchling\"]\n",[343,2728,2729],{"class":345,"line":460},[343,2730,2731],{},"build-backend = \"hatchling.build\"\n",[343,2733,2734],{"class":345,"line":466},[343,2735,392],{"emptyLinePlaceholder":391},[343,2737,2738],{"class":345,"line":472},[343,2739,2740],{},"# 加上 ruff 相關設置\n",[343,2742,2743],{"class":345,"line":478},[343,2744,2175],{},[343,2746,2747],{"class":345,"line":484},[343,2748,2180],{},[343,2750,2751],{"class":345,"line":490},[343,2752,2753],{},"target-version = \"py310\"  # 改成你的 python 版本\n",[343,2755,2756],{"class":345,"line":495},[343,2757,392],{"emptyLinePlaceholder":391},[343,2759,2760],{"class":345,"line":501},[343,2761,2194],{},[343,2763,2764],{"class":345,"line":507},[343,2765,2766],{},"select = [\n",[343,2768,2769],{"class":345,"line":513},[343,2770,2771],{},"    \"E\",   # pycodestyle errors\n",[343,2773,2774],{"class":345,"line":518},[343,2775,2776],{},"    \"W\",   # pycodestyle warnings\n",[343,2778,2779],{"class":345,"line":524},[343,2780,2781],{},"    \"F\",   # pyflakes\n",[343,2783,2784],{"class":345,"line":530},[343,2785,2786],{},"    \"I\",   # isort\n",[343,2788,2789],{"class":345,"line":536},[343,2790,2791],{},"    \"C\",   # flake8-comprehensions\n",[343,2793,2794],{"class":345,"line":542},[343,2795,2796],{},"    \"B\",   # flake8-bugbear\n",[343,2798,2799],{"class":345,"line":547},[343,2800,2801],{},"    \"UP\",  # pyupgrade\n",[343,2803,2804],{"class":345,"line":553},[343,2805,2076],{},[343,2807,2808],{"class":345,"line":559},[343,2809,2810],{},"ignore = [\n",[343,2812,2813],{"class":345,"line":565},[343,2814,2815],{},"    \"E501\",  # line too long, handled by black\n",[343,2817,2818],{"class":345,"line":571},[343,2819,2820],{},"    \"B008\",  # do not perform function calls in argument defaults\n",[343,2822,2823],{"class":345,"line":577},[343,2824,2825],{},"    \"C901\",  # too complex\n",[343,2827,2828],{"class":345,"line":583},[343,2829,2830],{},"    \"W191\",  # indentation contains tabs\n",[343,2832,2833],{"class":345,"line":589},[343,2834,2076],{},[343,2836,2837],{"class":345,"line":595},[343,2838,2839],{},"exclude = [\n",[343,2841,2842],{"class":345,"line":601},[343,2843,2844],{},"    \"tests/*\",\n",[343,2846,2847],{"class":345,"line":607},[343,2848,2849],{},"    \"venv/*\",\n",[343,2851,2852],{"class":345,"line":613},[343,2853,2854],{},"    \"*/site-packages/*\",\n",[343,2856,2857],{"class":345,"line":619},[343,2858,2859],{},"    \"*/static/*\",\n",[343,2861,2862],{"class":345,"line":624},[343,2863,2864],{},"    \"wsgi.py\",\n",[343,2866,2867],{"class":345,"line":630},[343,2868,2076],{},[343,2870,2871],{"class":345,"line":635},[343,2872,2873],{},"fixable = [\"ALL\"]\n",[343,2875,2876],{"class":345,"line":640},[343,2877,2878],{},"unfixable = []\n",[343,2880,2881],{"class":345,"line":645},[343,2882,392],{"emptyLinePlaceholder":391},[343,2884,2885],{"class":345,"line":650},[343,2886,2887],{},"[tool.ruff.lint.per-file-ignores]\n",[343,2889,2890],{"class":345,"line":655},[343,2891,2892],{},"\"tests/**/*\" = [\"S101\"]  # 測試中允許 assert\n",[343,2894,2895],{"class":345,"line":661},[343,2896,392],{"emptyLinePlaceholder":391},[343,2898,2899],{"class":345,"line":666},[343,2900,2901],{},"[tool.ruff.format]\n",[343,2903,2904],{"class":345,"line":672},[343,2905,2906],{},"quote-style = \"double\"\n",[343,2908,2909],{"class":345,"line":678},[343,2910,2911],{},"indent-style = \"space\"\n",[11,2913,2915],{"id":2914},"_5-安裝依賴","5. 安裝依賴",[333,2917,2919],{"className":1039,"code":2918,"language":1041,"meta":338,"style":338},"# 這會產生 uv.lock，第一次加上 --frozen 就好\nuv sync --frozen\n",[340,2920,2921,2926],{"__ignoreMap":338},[343,2922,2923],{"class":345,"line":346},[343,2924,2925],{"class":2407},"# 這會產生 uv.lock，第一次加上 --frozen 就好\n",[343,2927,2928,2930,2933],{"class":345,"line":352},[343,2929,1912],{"class":1048},[343,2931,2932],{"class":1052}," sync",[343,2934,2935],{"class":1059}," --frozen\n",[11,2937,2939],{"id":2938},"_6-ide-設置如果你使用-vs-code","6. IDE 設置（如果你使用 VS Code）",[1583,2941,2942,2945,2948],{},[34,2943,2944],{},"安裝 VS Code extensions（如果你使用 VS Code，可以去找一下 ruff 的 extension，可以使用 IDE 的修復輔助）",[34,2946,2947],{},"Ctrl + Shift + P：>Format Document With… 將 ruff 設為 default formatter",[34,2949,2950],{},"確保 format on save = true",[11,2952,2954],{"id":2953},"_7-清理現有代碼","7. 清理現有代碼",[333,2956,2958],{"className":1039,"code":2957,"language":1041,"meta":338,"style":338},"# 先看有幾個錯誤，以及可以使用 --fix 自動修復的項目\nruff check\n\n# 自動修復\nruff check --fix\n\n# 接下來開始清理 code style 問題\n# 啟用 hot reload，可以快速修復已存在的錯誤\nruff check --watch\n",[340,2959,2960,2965,2972,2976,2981,2991,2995,3000,3005],{"__ignoreMap":338},[343,2961,2962],{"class":345,"line":346},[343,2963,2964],{"class":2407},"# 先看有幾個錯誤，以及可以使用 --fix 自動修復的項目\n",[343,2966,2967,2969],{"class":345,"line":352},[343,2968,2270],{"class":1048},[343,2970,2971],{"class":1052}," check\n",[343,2973,2974],{"class":345,"line":358},[343,2975,392],{"emptyLinePlaceholder":391},[343,2977,2978],{"class":345,"line":364},[343,2979,2980],{"class":2407},"# 自動修復\n",[343,2982,2983,2985,2988],{"class":345,"line":370},[343,2984,2270],{"class":1048},[343,2986,2987],{"class":1052}," check",[343,2989,2990],{"class":1059}," --fix\n",[343,2992,2993],{"class":345,"line":376},[343,2994,392],{"emptyLinePlaceholder":391},[343,2996,2997],{"class":345,"line":382},[343,2998,2999],{"class":2407},"# 接下來開始清理 code style 問題\n",[343,3001,3002],{"class":345,"line":388},[343,3003,3004],{"class":2407},"# 啟用 hot reload，可以快速修復已存在的錯誤\n",[343,3006,3007,3009,3011],{"class":345,"line":395},[343,3008,2270],{"class":1048},[343,3010,2987],{"class":1052},[343,3012,3013],{"class":1059}," --watch\n",[11,3015,3017],{"id":3016},"_8-執行專案","8. 執行專案",[333,3019,3021],{"className":1039,"code":3020,"language":1041,"meta":338,"style":338},"uv run pytest\nuv run python src/main.py\n",[340,3022,3023,3032],{"__ignoreMap":338},[343,3024,3025,3027,3029],{"class":345,"line":346},[343,3026,1912],{"class":1048},[343,3028,1056],{"class":1052},[343,3030,3031],{"class":1052}," pytest\n",[343,3033,3034,3036,3038,3040],{"class":345,"line":352},[343,3035,1912],{"class":1048},[343,3037,1056],{"class":1052},[343,3039,2561],{"class":1052},[343,3041,3042],{"class":1052}," src/main.py\n",[11,3044,3046],{"id":3045},"_9-dockerfile-範例","9. Dockerfile 範例",[333,3048,3052],{"className":3049,"code":3050,"language":3051,"meta":338,"style":338},"language-dockerfile shiki shiki-themes github-light github-dark","# 使用官方 Python 基礎映像\nFROM python:3.11-slim\n\n# 設定工作目錄\nWORKDIR /app\n\n# 安裝 uv\nCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/\n\n# 複製依賴文件\nCOPY pyproject.toml uv.lock ./\n\n# 安裝依賴（不包含開發依賴）\nRUN uv sync --frozen --no-dev\n\n# 複製應用程式代碼\nCOPY . .\n\n# 設定 Python 路徑\nENV PATH=\"/app/.venv/bin:$PATH\"\n\n# 暴露端口（根據你的應用程式調整）\nEXPOSE 8000\n\n# 執行應用程式\nCMD [\"python\", \"src/main.py\"]\n","dockerfile",[340,3053,3054,3059,3064,3068,3073,3078,3082,3086,3091,3095,3100,3105,3109,3114,3119,3123,3128,3133,3137,3142,3147,3151,3156,3161,3165,3170],{"__ignoreMap":338},[343,3055,3056],{"class":345,"line":346},[343,3057,3058],{},"# 使用官方 Python 基礎映像\n",[343,3060,3061],{"class":345,"line":352},[343,3062,3063],{},"FROM python:3.11-slim\n",[343,3065,3066],{"class":345,"line":358},[343,3067,392],{"emptyLinePlaceholder":391},[343,3069,3070],{"class":345,"line":364},[343,3071,3072],{},"# 設定工作目錄\n",[343,3074,3075],{"class":345,"line":370},[343,3076,3077],{},"WORKDIR /app\n",[343,3079,3080],{"class":345,"line":376},[343,3081,392],{"emptyLinePlaceholder":391},[343,3083,3084],{"class":345,"line":382},[343,3085,2408],{},[343,3087,3088],{"class":345,"line":388},[343,3089,3090],{},"COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/\n",[343,3092,3093],{"class":345,"line":395},[343,3094,392],{"emptyLinePlaceholder":391},[343,3096,3097],{"class":345,"line":401},[343,3098,3099],{},"# 複製依賴文件\n",[343,3101,3102],{"class":345,"line":407},[343,3103,3104],{},"COPY pyproject.toml uv.lock ./\n",[343,3106,3107],{"class":345,"line":413},[343,3108,392],{"emptyLinePlaceholder":391},[343,3110,3111],{"class":345,"line":419},[343,3112,3113],{},"# 安裝依賴（不包含開發依賴）\n",[343,3115,3116],{"class":345,"line":425},[343,3117,3118],{},"RUN uv sync --frozen --no-dev\n",[343,3120,3121],{"class":345,"line":431},[343,3122,392],{"emptyLinePlaceholder":391},[343,3124,3125],{"class":345,"line":437},[343,3126,3127],{},"# 複製應用程式代碼\n",[343,3129,3130],{"class":345,"line":443},[343,3131,3132],{},"COPY . .\n",[343,3134,3135],{"class":345,"line":449},[343,3136,392],{"emptyLinePlaceholder":391},[343,3138,3139],{"class":345,"line":455},[343,3140,3141],{},"# 設定 Python 路徑\n",[343,3143,3144],{"class":345,"line":460},[343,3145,3146],{},"ENV PATH=\"/app/.venv/bin:$PATH\"\n",[343,3148,3149],{"class":345,"line":466},[343,3150,392],{"emptyLinePlaceholder":391},[343,3152,3153],{"class":345,"line":472},[343,3154,3155],{},"# 暴露端口（根據你的應用程式調整）\n",[343,3157,3158],{"class":345,"line":478},[343,3159,3160],{},"EXPOSE 8000\n",[343,3162,3163],{"class":345,"line":484},[343,3164,392],{"emptyLinePlaceholder":391},[343,3166,3167],{"class":345,"line":490},[343,3168,3169],{},"# 執行應用程式\n",[343,3171,3172],{"class":345,"line":495},[343,3173,3174],{},"CMD [\"python\", \"src/main.py\"]\n",[2266,3176],{},[1903,3178,1435],{"id":1435},[31,3180,3181,3188,3195],{},[34,3182,3183],{},[1441,3184,3187],{"href":3185,"rel":3186},"https://astral.sh/",[1445],"Astral: High-performance Python tooling",[34,3189,3190],{},[1441,3191,3194],{"href":3192,"rel":3193},"https://docs.astral.sh/uv/",[1445],"uv Documentation",[34,3196,3197],{},[1441,3198,3201],{"href":3199,"rel":3200},"https://docs.astral.sh/ruff/",[1445],"Ruff Documentation",[1462,3203,3204],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":338,"searchDepth":352,"depth":352,"links":3206},[3207,3208,3212,3213,3214,3215,3216,3218,3219,3220,3221,3222],{"id":1923,"depth":352,"text":1924},{"id":1957,"depth":352,"text":1958,"children":3209},[3210,3211],{"id":2008,"depth":358,"text":2009},{"id":2240,"depth":358,"text":2240},{"id":2281,"depth":352,"text":2282},{"id":2396,"depth":352,"text":2397},{"id":2438,"depth":352,"text":2439},{"id":2543,"depth":352,"text":2544},{"id":2622,"depth":352,"text":3217},"4. 設定 pyproject.toml",{"id":2914,"depth":352,"text":2915},{"id":2938,"depth":352,"text":2939},{"id":2953,"depth":352,"text":2954},{"id":3016,"depth":352,"text":3017},{"id":3045,"depth":352,"text":3046},"介紹 uv 和 Ruff，兩個最佳的 Python 工具鏈，以及如何遷移到 uv 和 Ruff。",{},"/blog/uv-and-ruff",{"title":1899,"description":3223},"blog/uv-and-ruff",[1496,3229,3230,1912,3231],"Tooling","Development","Ruff","-zRvt4hekmdP0cJ82m1OICrMfHIMFKCtweqM3DQ743s",{"id":3234,"title":3235,"author":6,"body":3236,"category":1486,"date":3560,"description":3561,"draft":1489,"extension":1490,"image":3562,"meta":3563,"navigation":391,"path":3564,"seo":3565,"stem":3566,"tags":3567,"__hash__":3575},"blog/blog/20260304-troublemaker.md","關於我一天在公司 AWS 開發帳號花掉 14000 鎂的那回事",{"type":8,"value":3237,"toc":3542},[3238,3245,3248,3252,3259,3262,3271,3280,3287,3290,3304,3307,3312,3315,3318,3325,3328,3331,3334,3338,3341,3345,3348,3353,3362,3374,3377,3385,3390,3393,3396,3401,3404,3411,3416,3420,3423,3434,3438,3441,3448,3455,3458,3461,3464,3467,3470,3473,3476,3479,3483,3486,3489,3495,3499,3502,3513,3517,3520,3527,3530,3533,3536,3539],[16,3239,3240,3241,3244],{},"身為工程師，你可能聽過同事不小心把 Production 資料庫砍了、或是忘記關 EC2 多燒了幾百塊。但你有聽過",[37,3242,3243],{},"用雲服務一天燒掉 14,000 美金","的嗎？",[16,3246,3247],{},"沒錯，這件事發生在我身上...嗎？",[11,3249,3251],{"id":3250},"tldr","TL;DR",[16,3253,3254,3255,3258],{},"在公司開發帳號 survey AWS Bedrock AgentCore Policy 功能，試用了 Cedar Policy Generator。幾天後帳單突然出現一筆 ",[37,3256,3257],{},"$14,000+ USD 的單日費用","。經過 CloudTrail 排查確認是 Cedar Generator 觸發的異常計費，最終 AWS 承認是 Bedrock 端的計費 bug，修復並校正了帳單。",[11,3260,3261],{"id":3261},"事發經過",[16,3263,3264,3265,3270],{},"3/4 我因為看到 ",[1441,3266,3269],{"href":3267,"rel":3268},"https://aws.amazon.com/bedrock/agentcore/",[1445],"AgentCore"," 新 Feature - Policy 剛上線，所以需要 Survey 一下。",[16,3272,3273,3274,3279],{},"AgentCore Policy 讓你可以用 ",[1441,3275,3278],{"href":3276,"rel":3277},"https://www.cedarpolicy.com/",[1445],"Cedar"," 語言來定義 Agent 的授權策略，控制 Agent 能存取哪些工具和資源。Cedar 是 AWS 開源的授權語言，語法可讀性很高，設計上是要讓非工程師也能看得懂的那種。",[16,3281,3282,3283,3286],{},"而 AgentCore 很貼心地提供了一個 ",[37,3284,3285],{},"Natural Language Policy Generator","——你用自然語言描述你要的權限規則，它就幫你生成對應的 Cedar Policy。聽起來很 User-friendly 對吧？",[16,3288,3289],{},"我就照著文件走了一遍流程：",[1583,3291,3292,3295,3298,3301],{},[34,3293,3294],{},"建了一個 Policy Engine",[34,3296,3297],{},"試了幾次 Cedar Policy Generator（用自然語言描述轉成 Cedar）",[34,3299,3300],{},"確認功能可以正常運作",[34,3302,3303],{},"收工，覺得這功能還不錯",[16,3305,3306],{},"整個過程大概就一兩個小時，正常的 survey 流程。",[16,3308,3309],{},[37,3310,3311],{},"然後就沒有然後了——直到五天後。",[11,3313,3314],{"id":3314},"帳單爆炸",[16,3316,3317],{},"後來請假看完中華隊在 WBC 的比賽，打贏韓國那場實在很感動，回來上班的第一天，3/9 下午，Slack突然被狂 tag，我被我們公司的 Billing Manager & Team Lead 問為何會有一筆費用產生，我也趕緊地打開CE，一個讓我愣住的數字：",[1511,3319,3320],{},[16,3321,3322],{},[37,3323,3324],{},"3/4 單日Agentcore產生了 $14,000+ USD的費用",[16,3326,3327],{},"我先是以為自己眼花了。重新整理頁面，數字還在那裡。",[16,3329,3330],{},"完了... 哪裡搞錯了吧？",[11,3332,3333],{"id":3333},"排查過程",[26,3335,3337],{"id":3336},"step-1先開-support-ticket","Step 1：先開 Support Ticket",[16,3339,3340],{},"不管三七二十一，先開 AWS Support Ticket 回報異常帳單。把時間範圍、帳號資訊、異常金額都附上去，讓 Support 那邊開始調查。",[26,3342,3344],{"id":3343},"step-2自己也同步排查","Step 2：自己也同步排查",[16,3346,3347],{},"等 Support 回覆的同時，我也開始自己排查紀錄。",[16,3349,3350],{},[37,3351,3352],{},"確認 AgentCore Policy 的收費機制",[16,3354,3355,3356,3361],{},"根據 ",[1441,3357,3360],{"href":3358,"rel":3359},"https://aws.amazon.com/bedrock/agentcore/pricing/",[1445],"AgentCore 定價頁面","，Policy 的計費主要是：",[31,3363,3364,3371],{},[34,3365,3366,3367,3370],{},"Cedar Policy Generator：",[37,3368,3369],{},"按 input token 數量計費","（每 1,000 tokens）",[34,3372,3373],{},"Policy Engine 的 Authorization 請求：按請求數計費",[16,3375,3376],{},"稍微思考一下：",[1583,3378,3379,3382],{},[34,3380,3381],{},"我當天的使用量根本不可能撐到這個金額。就算我瘋狂打 Generator 也打不出 $14,000，同事跟我說 Billing 那邊顯示 Policy 使用了 109 1M Tokens (接近 1.1 億，我甚至不知道一天要怎麼用掉那麼多 Tokens...)。",[34,3383,3384],{},"我的 Policy Engine 並沒有 Attach Gateway，所以也沒有真正的使用 Policy 這個 Feature。",[16,3386,3387],{},[37,3388,3389],{},"確認 Runtime 沒有產生額外費用",[16,3391,3392],{},"AgentCore Runtime 是按 CPU 和記憶體的秒級消耗來計費的。我需要確保沒有遺留的 Runtime 在背景持續運作，或者跟 Gateway 掛鉤產生連鎖費用。",[16,3394,3395],{},"檢查結果：沒有任何遺留資源在跑。",[16,3397,3398],{},[37,3399,3400],{},"CloudTrail",[16,3402,3403],{},"我拉出了那段時間的 CloudTrail 紀錄，逐筆檢查跟 AgentCore 相關的 API 呼叫。",[16,3405,3406,3407,3410],{},"最終鎖定是 ",[340,3408,3409],{},"StartPolicyGeneration"," 這個 API 呼叫。從紀錄上看，我確實只有呼叫了3次，請求量完全不合理對應到那個帳單金額。",[16,3412,3413],{},[37,3414,3415],{},"結論：應該不是我的使用量有問題，是計費那邊有問題。",[26,3417,3419],{"id":3418},"step-3跟-support-同步","Step 3：跟 Support 同步",[16,3421,3422],{},"我把 CloudTrail 的排查結果整理好，回覆到 Support Ticket 上。附上了：",[31,3424,3425,3428,3431],{},[34,3426,3427],{},"明確的 API 呼叫時間和次數",[34,3429,3430],{},"計費金額的不合理性說明",[34,3432,3433],{},"我這邊已經確認沒有遺留資源",[26,3435,3437],{"id":3436},"step-4aws-確認是計費-bug","Step 4：AWS 確認是計費 Bug",[16,3439,3440],{},"AWS Support 將 case 轉給了 Bedrock 團隊。Bedrock 團隊調查後確認：",[1511,3442,3443],{},[16,3444,3445],{},[37,3446,3447],{},"Cedar Policy Generator 存在計費問題，導致實際計費金額遠超正常使用量應有的費用。",[16,3449,3450,3451,3454],{},"他們修復了這個計費 bug，並且",[37,3452,3453],{},"校正了帳單","。",[16,3456,3457],{},"結案。",[11,3459,3460],{"id":3460},"心理狀態",[16,3462,3463],{},"看到 $14,000 這個數字的時候，腦袋裡跑過各種最壞的劇本——會不會要自己賠？這是我好幾個月的薪水總和，光想就覺得可怕。",[16,3465,3466],{},"即使理性上告訴自己先查原因再說，甚至基本上確定不是我這邊的問題，但焦慮感還是壓不住。",[16,3468,3469],{},"這幾天嚴重睡眠不足，每天躺在床上腦袋還在轉「到底是哪裡出問題」。腸胃也跟著出狀況，一直拉肚子。壓力對身體的影響比我想像中來得直接。",[16,3471,3472],{},"最後當 CloudTrail 的證據越來越明確指向計費問題，Support 也確認 Bedrock 團隊發現問題並正在進行修復，我的心情才開始慢慢平復。",[11,3474,3475],{"id":3475},"事後反思",[16,3477,3478],{},"雖然這次最後證實是 AWS 的計費 bug，但整個事件讓我學到了很多。",[26,3480,3482],{"id":3481},"_1-工作紀錄真的很重要","1. 工作紀錄真的很重要",[16,3484,3485],{},"當下會感到慌張的原因，很大一部分來自 Context 不足，一來我不確定 Agentcore Policy 是如何計費，為何可以用到那麼貴？二來我不知道我5天前具體做了什麼操作。",[16,3487,3488],{},"如果我當初 survey 的時候沒有留下操作紀錄，排查的時候會更加困難。CloudTrail 能幫你查到 API 呼叫，但你自己當時在做什麼、為什麼做，這些 context 只有你自己知道。",[16,3490,3491,3494],{},[37,3492,3493],{},"養成習慣：每次操作不熟的雲服務時，簡單記錄一下你做了什麼。"," 不需要多詳細，一個簡單的筆記或是 Slack 訊息就夠了。關鍵時刻這些紀錄可以救你一命。",[26,3496,3498],{"id":3497},"_2-使用前搞懂計費方式","2. 使用前搞懂計費方式",[16,3500,3501],{},"這聽起來像廢話，但真的很多人（包括我）在 survey 新服務的時候會直接跳進去玩，不會先仔細看定價頁面。",[31,3503,3504,3507,3510],{},[34,3505,3506],{},"開始 survey 前，先看過定價頁面",[34,3508,3509],{},"特別注意按量計費的服務，搞清楚「量」是怎麼定義的",[34,3511,3512],{},"開發帳號最好設定 Budget Alert，超過閾值自動通知",[26,3514,3516],{"id":3515},"_3-先了解公司的處理流程","3. 先了解公司的處理流程",[16,3518,3519],{},"事發的時候，我其實不太確定公司對這種事情的態度和處理方式。後來跟主管報告時，主管第一句話就是：",[1511,3521,3522],{},[16,3523,3524],{},[37,3525,3526],{},"「不可能叫你賠的，先搞清楚狀況就好。」",[16,3528,3529],{},"這句話讓我放下了很大的心理負擔。每間公司的文化不同，但我想大部分公司都不會因為合理操作導致的意外費用而讓員工賠償。",[11,3531,3532],{"id":3532},"結語",[16,3534,3535],{},"回頭看這件事，蠻慶幸最後不是我的問題，也慶幸公司的主管很 Nice",[16,3537,3538],{},"這次經歷讓我建立起了面對雲端成本異常的 SOP。以前覺得帳單管理是 FinOps 團隊的事，現在覺得每個會碰到雲服務的工程師都應該有基本的成本意識。",[16,3540,3541],{},"題外話，這讓我想到我大學時期第一次買股票的時候，我買了3股台積電花了大概 2000塊吧，當時每天看著損益正負三四百塊就能很影響我的心情，影響我做事與學習的效率，直到今天可能每天的損益就是一個月的薪水，我還是照常做著自己的事情。我想有了這次經驗，肯定能讓我在未來遇到類似的事情的時候心態更穩健吧。",{"title":338,"searchDepth":352,"depth":352,"links":3543},[3544,3545,3546,3547,3553,3554,3559],{"id":3250,"depth":352,"text":3251},{"id":3261,"depth":352,"text":3261},{"id":3314,"depth":352,"text":3314},{"id":3333,"depth":352,"text":3333,"children":3548},[3549,3550,3551,3552],{"id":3336,"depth":358,"text":3337},{"id":3343,"depth":358,"text":3344},{"id":3418,"depth":358,"text":3419},{"id":3436,"depth":358,"text":3437},{"id":3460,"depth":352,"text":3460},{"id":3475,"depth":352,"text":3475,"children":3555},[3556,3557,3558],{"id":3481,"depth":358,"text":3482},{"id":3497,"depth":358,"text":3498},{"id":3515,"depth":358,"text":3516},{"id":3532,"depth":352,"text":3532},"2026-03-18","Survey AWS AgentCore Policy完，過了幾天以後才發現帳單噴了14000...","/images/blog/20260304-troublemaker/banner.png",{},"/blog/20260304-troublemaker",{"title":3235,"description":3561},"blog/20260304-troublemaker",[3568,3569,3570,3571,3572,3573,3574],"AWS","雲端","成本控管","經驗分享","踩雷","DevOps","雲服務","6QyxVCpq8RjfovhyvoS0butlFVU7bQKJ1pfTGyL4M1Q",1774237783318]