最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python異步編程入門協(xié)程到底是什么與線程、進(jìn)程的區(qū)別

 更新時間:2025年12月24日 11:06:41   作者:dazzle  
這篇文章介紹了Python異步編程中的協(xié)程,解釋了它們?nèi)绾谓鉀Q同步編程中的阻塞問題,并與線程和進(jìn)程進(jìn)行了比較,感興趣的朋友跟隨小編一起聊聊這個話題

Python異步編程入門:協(xié)程到底是什么?

你可能已經(jīng)遇到過這樣的場景:寫了一個爬蟲,但請求網(wǎng)頁時總是卡??;或者搭了個Web服務(wù),同時處理幾個請求就變得慢吞吞。這時候別人可能會告訴你:“試試異步編程吧,用協(xié)程。”

但協(xié)程到底是什么?它跟線程、進(jìn)程有什么區(qū)別?為什么大家都在說asyncio?今天咱們就來好好聊聊這個話題。

從瓶頸說起:程序為什么卡?

想象一下你去快餐店點餐。如果只有一個收銀員,前面的人點單特別慢,你就得一直等著——這就是典型的同步阻塞。你的代碼可能正在等網(wǎng)絡(luò)響應(yīng)、等文件讀寫、等數(shù)據(jù)庫查詢,而CPU就在那里空轉(zhuǎn)。

換成多線程呢?好比開了幾個收銀臺,同時服務(wù)。但每個收銀臺還是要等顧客慢慢掏錢、找零,而且收銀臺之間還要協(xié)調(diào)(比如共用一臺打印機),協(xié)調(diào)不好就會出問題。在程序里,這就是線程安全問題和上下文切換開銷。

協(xié)程的做法不太一樣:一個收銀員同時接待多個顧客。A顧客在掏錢包時,收銀員轉(zhuǎn)頭問B顧客要什么;B顧客猶豫時,又去給C顧客結(jié)賬。看起來收銀員一直在忙,但實際沒有真的“同時”做多件事,只是切換得很快。

協(xié)程的本質(zhì):可暫停的函數(shù)

協(xié)程本質(zhì)上是一種特殊的函數(shù),它能在執(zhí)行到一半時暫停,把控制權(quán)交出去,過一會兒又能從暫停的地方繼續(xù)執(zhí)行。

async def fetch_data():
    print("開始請求數(shù)據(jù)")
    await asyncio.sleep(2)  # 在這里暫停,讓其他協(xié)程運行
    print("數(shù)據(jù)返回了")

這個await asyncio.sleep(2)就像告訴程序:“我先歇會兒,你去干點別的,2秒后再叫我。”這種機制讓單個線程可以處理多個任務(wù),而不是傻等。

你可能會問,這和生成器(generator)的yield有點像吧?確實,Python的協(xié)程就是從生成器進(jìn)化來的。早期的協(xié)程就是用yield實現(xiàn)的,但async/await語法更清晰,專門為異步編程設(shè)計。

協(xié)程 vs 線程 vs 進(jìn)程:什么時候選哪個?

先看個簡單的對比:

  • 多進(jìn)程:開多個廚房,各自獨立,但溝通成本高(進(jìn)程間通信)。適合CPU密集型計算,能利用多核優(yōu)勢。
  • 多線程:一個廚房里多個廚師,共用設(shè)備,但要小心互相干擾(線程安全)。適合I/O操作,但線程數(shù)量多了開銷大。
  • 協(xié)程:一個廚師同時照看幾口鍋,哪口鍋需要攪拌就攪一下,不需要時就處理別的。適合高并發(fā)I/O,但單個協(xié)程不能阻塞。

協(xié)程最大的優(yōu)勢就是輕量。創(chuàng)建一個線程需要幾MB內(nèi)存,而一個協(xié)程可能只要幾KB。一個線程能跑成千上萬個協(xié)程,切換開銷極小,特別適合I/O密集的場景——比如網(wǎng)絡(luò)請求、文件讀寫,這些操作大部分時間都在等,而不是真的在計算。

但協(xié)程不是銀彈。如果你的任務(wù)是純計算型的(比如圖像處理、復(fù)雜算法),協(xié)程幫不上什么忙,因為計算本身不會主動讓出CPU。這時候多進(jìn)程或者直接優(yōu)化算法可能更有效。

Python協(xié)程的演進(jìn):從yield到async/await

Python實現(xiàn)協(xié)程的方式有過幾次大的變化,了解這段歷史有助于理解為什么現(xiàn)在是這樣的設(shè)計:

# 1. 生成器時代(Python 2.5+) - 用yield模擬
def old_style_coroutine():
    print("開始")
    data = yield "請給我數(shù)據(jù)"
    print(f"收到數(shù)據(jù):{data}")
# 使用方式:
coro = old_style_coroutine()
next(coro)  # 啟動,輸出"開始",返回"請給我數(shù)據(jù)"
coro.send("hello")  # 發(fā)送數(shù)據(jù),輸出"收到數(shù)據(jù):hello"
# 2. 裝飾器時代(Python 3.4)- 引入asyncio
@asyncio.coroutine
def decorator_style():
    yield from asyncio.sleep(1)
    print("完成")
# 3. 現(xiàn)代寫法(Python 3.5+)- 現(xiàn)在的標(biāo)準(zhǔn)
async def modern_coroutine():
    await asyncio.sleep(1)
    print("完成")

現(xiàn)在基本都用async/await這套語法,清晰直觀。但你可能在舊代碼里看到前兩種寫法,知道它們是一回事就行。有趣的是,async/await在C#、JavaScript等語言中也是類似的語法,學(xué)會一次,多語言受益。

深入理解async和await

第一次見async def可能會有點懵:這函數(shù)怎么調(diào)用后不執(zhí)行?。?/p>

async def hello():
    print("Hello, async!")
coro = hello()  # 注意:這里不會打印任何東西!
print(type(coro))  # <class 'coroutine'>

這里有個重要概念:協(xié)程函數(shù)被調(diào)用時返回的是一個協(xié)程對象,而不是直接執(zhí)行。要讓它跑起來,需要事件循環(huán)的調(diào)度。這就像給了你一張任務(wù)卡,但需要有人(事件循環(huán))來執(zhí)行它。

await則是協(xié)程世界里的“等待”符號。但它不是阻塞等待,而是“我這兒暫時沒事,你先去忙別的”。

async def main():
    print("開始煮面")
    print("水燒開了,下面條")
    await asyncio.sleep(3)  # 等面煮熟,但程序可以去干別的
    print("面熟了,撈起來")
    print("開始炒菜")
    await asyncio.sleep(2)  # 等菜炒熟
    print("菜好了")
    print("開飯!")

有個常見的誤解:await就是異步。其實await本身不創(chuàng)造異步,它只是告訴程序“這里可以切換”。真正的異步能力來自那些支持異步的操作,比如asyncio.sleep()、aiohttp的網(wǎng)絡(luò)請求等。

另一個常見誤區(qū):以為用了async/await就自動變快。實際上,如果所有操作都是順序的await,那和同步?jīng)]什么區(qū)別。真正的并發(fā)要靠asyncio.gather()、asyncio.wait()這樣的結(jié)構(gòu)。

事件循環(huán):協(xié)程的調(diào)度中心

如果協(xié)程是演員,事件循環(huán)就是導(dǎo)演。導(dǎo)演決定哪個演員什么時候上場、什么時候休息。

在Python 3.7之前,你需要自己管理事件循環(huán):

# Python 3.6及以前
import asyncio
async def task():
    print("任務(wù)執(zhí)行中")
    await asyncio.sleep(1)
# 手動管理事件循環(huán)
loop = asyncio.get_event_loop()
loop.run_until_complete(task())
loop.close()

Python 3.7引入了asyncio.run(),簡化了這一切:

# Python 3.7+
async def main():
    await task()
asyncio.run(main())  # 一行搞定

asyncio.run()幫我們做了三件事:創(chuàng)建新的事件循環(huán)、運行協(xié)程、關(guān)閉循環(huán)。對于大多數(shù)應(yīng)用,這就夠了。

事件循環(huán)的工作方式有點像餐廳的叫號系統(tǒng):不斷檢查有沒有新的“事件”(比如網(wǎng)絡(luò)數(shù)據(jù)到達(dá)、定時器到期),然后喚醒對應(yīng)的協(xié)程繼續(xù)工作。它維護(hù)著一個待辦事項列表,哪個能處理就處理哪個。

實戰(zhàn)對比:同步 vs 異步下載

理論說了這么多,來個實際例子感受一下區(qū)別。

假設(shè)你要下載10張網(wǎng)絡(luò)圖片,用傳統(tǒng)同步方式大概是這樣:

import requests
import time
def download_sync(url, filename):
    response = requests.get(url)
    with open(filename, 'wb') as f:
        f.write(response.content)
urls = [...]  # 10個圖片URL
start = time.time()
for i, url in enumerate(urls):
    download_sync(url, f"image_{i}.jpg")
    print(f"下載完第{i+1}張")
print(f"總耗時: {time.time()-start:.2f}秒")
# 如果每張圖要1秒,這里大概要10秒

這是典型的順序執(zhí)行,一張下完再下一張,大部分時間都在等網(wǎng)絡(luò)響應(yīng)。

換成協(xié)程版本:

import aiohttp
import asyncio
import time
async def download_async(session, url, filename):
    async with session.get(url) as response:
        content = await response.read()
        with open(filename, 'wb') as f:
            f.write(content)
async def main():
    urls = [...]  # 同樣的10個URL
    async with aiohttp.ClientSession() as session:
        tasks = []
        for i, url in enumerate(urls):
            task = download_async(session, url, f"image_{i}.jpg")
            tasks.append(task)
        await asyncio.gather(*tasks)  # 并發(fā)下載!
start = time.time()
asyncio.run(main())
print(f"總耗時: {time.time()-start:.2f}秒")
# 可能只要1-2秒就全部下載完了

區(qū)別很明顯:同步版本是等一張下完再下一張;協(xié)程版本是同時發(fā)起所有請求,哪張先到就先處理哪張。對于I/O密集型任務(wù),速度提升可能非常顯著。

不過要注意,并不是所有場景都能這樣簡單替換。requests庫是同步的,不能直接在協(xié)程里用,需要換用異步版本的aiohttp。這也是很多初學(xué)者容易踩的坑:用了async/await,但調(diào)用的庫不支持異步,結(jié)果還是同步執(zhí)行。

常見的協(xié)程使用場景

什么時候該考慮用協(xié)程呢?這里有幾個典型場景:

  • Web服務(wù)器:比如用FastAPI、Sanic或aiohttp框架。每個請求都可能涉及數(shù)據(jù)庫查詢、外部API調(diào)用,用協(xié)程可以同時處理大量連接。
  • 網(wǎng)絡(luò)爬蟲:需要爬取大量網(wǎng)頁,每個網(wǎng)頁的下載都是I/O操作,協(xié)程能顯著提高效率。
  • 實時通信:聊天應(yīng)用、消息推送,需要維持大量長連接,協(xié)程的內(nèi)存開銷比線程小得多。
  • 批量文件處理:比如讀取大量文件、圖片處理(注意:圖片處理本身是CPU密集型,但讀取寫入文件是I/O密集型)。
  • 微服務(wù)調(diào)用:一個服務(wù)需要調(diào)用多個其他微服務(wù),然后合并結(jié)果,協(xié)程可以并行發(fā)起所有調(diào)用。

協(xié)程學(xué)習(xí)的難點和坑

剛開始學(xué)協(xié)程時,有幾個常見的困惑點:

第一,忘記加await

async def get_data():
    return "數(shù)據(jù)"
async def main():
    result = get_data()  # 錯誤!應(yīng)該加await
    print(result)  # 打印出來是個協(xié)程對象,不是字符串

第二,在同步函數(shù)里調(diào)用協(xié)程

def sync_func():
    data = await get_data()  # 語法錯誤!await只能在async函數(shù)里用

第三,阻塞事件循環(huán)

async def bad_example():
    # 這個函數(shù)會阻塞整個事件循環(huán)!
    time.sleep(5)  # 應(yīng)該用await asyncio.sleep(5)
    # CPU密集型計算也會阻塞
    sum(range(10**7))  # 應(yīng)該放到線程池里執(zhí)行

第四,以為所有庫都支持異步:實際上很多常用庫(比如requests、pymysql的同步API)都是同步的。需要用異步替代庫(aiohttp、aiomysql)或者把同步調(diào)用放到線程池里。

性能真的提升了嗎?一個簡單測試

我們來做個簡單實驗,看看協(xié)程在I/O密集型任務(wù)上的實際表現(xiàn):

import asyncio
import time
import aiohttp
import requests
# 測試用的URL,請求會延遲1秒返回
TEST_URL = "http://httpbin.org/delay/1"
# 同步版本
def sync_test(n=10):
    start = time.time()
    for i in range(n):
        requests.get(TEST_URL)
    return time.time() - start
# 異步版本
async def async_test(n=10):
    start = time.time()
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(TEST_URL) for _ in range(n)]
        await asyncio.gather(*tasks)
    return time.time() - start
# 運行測試
print("同步版,10個請求:")
print(f"耗時:{sync_test(10):.2f}秒")
print("\n異步版,10個請求:")
print(f"耗時:{asyncio.run(async_test(10)):.2f}秒")

在我的測試中,同步版大約需要10秒(順序執(zhí)行,每個1秒),而異步版大約只要1秒多(并發(fā)執(zhí)行)。當(dāng)請求數(shù)量增加到100時,差異會更加明顯。

現(xiàn)在開始用協(xié)程還太早嗎?

如果你的項目主要是CPU密集型計算(比如數(shù)據(jù)分析、圖像處理),協(xié)程帶來的提升可能有限。但如果是Web服務(wù)、爬蟲、聊天機器人這類I/O密集的應(yīng)用,協(xié)程幾乎成了標(biāo)配。

學(xué)習(xí)曲線呢?確實需要一點時間適應(yīng)。從同步思維切換到異步思維,就像從單線程轉(zhuǎn)到多線程一樣,需要重新考慮程序的組織方式。不過一旦掌握,代碼的性能和可讀性都會有很大改善。

Python的異步生態(tài)已經(jīng)相當(dāng)成熟了。Web框架有FastAPI(性能強悍,還自動生成API文檔)、Sanic;數(shù)據(jù)庫有aiomysql、asyncpg;HTTP客戶端有aiohttp;甚至機器學(xué)習(xí)領(lǐng)域也開始出現(xiàn)異步支持。

下一步該學(xué)什么?

今天我們從協(xié)程的基本概念講到了實際應(yīng)用,但這只是異步編程的起點。下次我們會深入更多實用話題:

  1. 協(xié)程間的通信:多個協(xié)程怎么安全地共享數(shù)據(jù)?asyncio.Queue怎么用?
  2. 同步原語:協(xié)程版本的鎖(Lock)、信號量(Semaphore)、事件(Event)是什么?
  3. 錯誤處理:協(xié)程里的異常怎么捕獲和處理?
  4. 與線程/進(jìn)程結(jié)合:如何在協(xié)程里調(diào)用同步代碼?怎么利用多核CPU?
  5. 實際項目結(jié)構(gòu):大型異步項目該怎么組織代碼?

這些話題我會在下一篇文章中詳細(xì)講解。如果你已經(jīng)躍躍欲試,建議先從一個小項目開始,比如寫個異步爬蟲,或者用FastAPI搭個簡單的Web服務(wù)。

你平時寫代碼時,遇到過哪些適合用協(xié)程解決的場景?或者對協(xié)程的哪些部分感到困惑?歡迎在評論區(qū)聊聊你的經(jīng)驗或問題,我們一起探討。

到此這篇關(guān)于Python異步編程入門:協(xié)程到底是什么?的文章就介紹到這了,更多相關(guān)Python協(xié)程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 在Python中使用gRPC的方法示例

    在Python中使用gRPC的方法示例

    這篇文章主要介紹了在Python中使用gRPC的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • Python JSON編解碼方式原理詳解

    Python JSON編解碼方式原理詳解

    這篇文章主要介紹了Python JSON編解碼方式原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01
  • Python實現(xiàn)查找字符串?dāng)?shù)組最長公共前綴示例

    Python實現(xiàn)查找字符串?dāng)?shù)組最長公共前綴示例

    這篇文章主要介紹了Python實現(xiàn)查找字符串?dāng)?shù)組最長公共前綴,涉及Python針對字符串的遍歷、判斷、計算等相關(guān)操作技巧,需要的朋友可以參考下
    2019-03-03
  • 基于Python編寫一個MP3分割工具

    基于Python編寫一個MP3分割工具

    這篇文章主要為大家詳細(xì)介紹了如何基于Python編寫一個MP3分割工具,可以將MP4轉(zhuǎn)成Mp3并且按時間或按歌曲分割,感興趣的小伙伴可以了解下
    2025-02-02
  • Python數(shù)據(jù)可視化Pyecharts庫實現(xiàn)桑葚圖效果

    Python數(shù)據(jù)可視化Pyecharts庫實現(xiàn)桑葚圖效果

    這篇文章主要介紹了Python數(shù)據(jù)可視化如何使用Pyecharts庫來實現(xiàn)桑葚圖效果圖,文中給出實現(xiàn)的示例代碼,有需要的朋友可以借鑒參考想,希望能夠有所幫助
    2021-09-09
  • Django路由匹配調(diào)試與排錯指南分享

    Django路由匹配調(diào)試與排錯指南分享

    這篇文章主要介紹了Django路由匹配調(diào)試與排錯指南,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-05-05
  • python中os.path.exits()的坑

    python中os.path.exits()的坑

    本文主要介紹了python中os.path.exits()的坑,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • 使用python+pandas讀寫xlsx格式中的數(shù)據(jù)

    使用python+pandas讀寫xlsx格式中的數(shù)據(jù)

    這篇文章主要介紹了使用python+pandas讀寫xlsx格式中的數(shù)據(jù),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下
    2022-08-08
  • 解決Python字典寫入文件出行首行有空格的問題

    解決Python字典寫入文件出行首行有空格的問題

    下面小編就為大家?guī)硪黄鉀QPython字典寫入文件出行首行有空格的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • 詳解Python中Addict模塊的使用方法

    詳解Python中Addict模塊的使用方法

    Addit是一個Python模塊,除了提供標(biāo)準(zhǔn)的字典語法外,Addit 生成的字典的值既可以使用屬性來獲取,也可以使用屬性進(jìn)行設(shè)置。本文將詳細(xì)講講它的使用方法,需要的可以參考一下
    2022-05-05

最新評論

板桥市| 洪湖市| 兴海县| 亳州市| 呼伦贝尔市| 珲春市| 青河县| 黔东| 桦甸市| 沾益县| 南通市| 东莞市| 绥阳县| 兴国县| 象山县| 云和县| 平陆县| 亚东县| 梅河口市| 合肥市| 丽水市| 方山县| 紫阳县| 忻城县| 伊春市| 白水县| 泗水县| 临潭县| 建德市| 台中县| 离岛区| 清河县| 南木林县| 沿河| 南投县| 如皋市| 昌江| 大渡口区| 虹口区| 高尔夫| 九龙县|