【Python】非同步任務如何實現提前返回

来源:https://www.cnblogs.com/123456feng/archive/2022/03/31/16082506.html
-Advertisement-
Play Games

前言 首先描述下業務場景,有一個介面,其中存在耗時操作,耗時操作的執行結果將寫入數據表中,不需要通過該介面直接返回。 因此理論上該介面可以在耗時操作執行結束前返回。本文中使用多線程後臺運行耗時操作,主線程提前返回,實現介面的提前返回。 此外,還嘗試使用協程實現,經驗證,協程適用於多任務併發處理,遇到 ...


前言

首先描述下業務場景,有一個介面,其中存在耗時操作,耗時操作的執行結果將寫入數據表中,不需要通過該介面直接返回。

因此理論上該介面可以在耗時操作執行結束前返回。本文中使用多線程後臺運行耗時操作,主線程提前返回,實現介面的提前返回。

此外,還嘗試使用協程實現,經驗證,協程適用於多任務併發處理,遇到耗時操作時自動切換任務,可以縮短多任務總的執行用時,而無法縮短單任務的執行用時,無法實現提前返回,因此不適用該場景。
在這裡插入圖片描述

1 同步任務

如下所示,定義任務,模擬耗時操作。

Python學習交流Q群:906715085###
import time

def task():   
 print("task start")  
   print("sleep...")   
    time.sleep(10)   
     print("task end")

 

如下所示,main 函數中執行任務,return true 用於模擬介面的返回。

如果最後執行 print(“task_result={}”.format®) 表明是同步執行,否則是非同步執行。

def main():    
print("main start")   
 task()    
 print("main end")   
  return True

if __name__ == '__main__':   
 r = main()    
print("task_result={}".format(r))

 

執行結果如下所示,表明是同步執行。

main start
task start
sleep...
task end
main end
task_result=True

 

對於介面來說,響應時間是非常重要的性能指標,因此可以通過非同步執行實現介面的提前返回,進而降低介面響應時間。

2 非同步任務

本文中提到的非同步執行指的是後臺運行某些代碼或功能,不阻塞主程式。

非同步執行的常規實現方式是使用多線程/多進程。

2.1 多線程

可以通過多線程的方式實現非同步執行,前提是不執行 join() 方法,否則將阻塞主線程。

註意使用多線程實現非同步操作時,需要將任務函數作為參數傳給線程的構造函數,因此需要修改代碼中任務函數的調用方式。

編寫併發代碼時經常這樣重構:把依序執行的for迴圈體改成函數,以便併發執行。

如下所示,定義 run_task 函數,並將原有的任務函數 task 作為參數傳入,其中創建子線程執行任務函數,調用 start() 方法啟動線程,不調用 join() 方法主線程繼續向下執行。

from threading import Thread

def run_task(f, *args, **kwargs):  
  t = Thread(target=f, args=args, kwargs=kwargs)  
  t.start()

def main():   
 print("main start")  
   run_task(task)  
     print("main end")   
      return True

if __name__ == '__main__':  
  r = main()    
  print("task_result={}".format(r))

 

執行結果如下所示,表明是非同步執行,main 函數在任務函數 task 執行結束前返回。

main start
task start
main end
sleep...
task_result=True
task end

 

直接通過多線程實現非同步任務的缺點是需要修改任務函數的調用方式,代碼的改動較大。

實際上可以通過 多線程+裝飾器 的方式將多線程包裝一層,在不改變任務函數調用方式的前提下實現非同步操作。

在這裡插入圖片描述

2.2 多線程 + 裝飾器

定義裝飾器,其中創建並啟動子線程,用於執行傳入的任務函數。本質上與上述多線程的實現相同。

def async_thread(f):    
def wrapper(*args, **kwargs):       
 t = Thread(target=f, args=args, kwargs=kwargs)        
 t.start()   
  return wrapper

 

將裝飾器置於任務函數的定義處,在不修改原函數定義的前提下,在代碼運行期間動態增加功能。

如下所示,通過 async_thread 將同步操作修改為非同步操作,同時不修改任務函數的調用方式。

執行函數時,調用 task() 而非 run_task()。

@async_thread
def task():    
print("task start")    
print("sleep...")    
time.sleep(10)    
print("task end")

def main():   
 print("main start")   
  # run_task(task)   
   task()   
    print("main end")    
    return True

 

執行結果如下所示,同樣是非同步執行。

main start
task start
sleep...
main end
task_result=True
task end

 

3 非同步框架

Python支持多種非同步框架,如 Cerely、Tornado、Twisted 等,後續詳細介紹。

4 協程

同樣,Python支持協程的多種調用方法。本文中使用 asyncio 模塊通過協程實現非同步操作。

在這裡插入圖片描述

4.1 單任務

如下所示,task 任務與同步代碼的區別包括:

•調用 asyncio 模塊的 @asyncio.coroutine 裝飾器,將生成器聲明為協程;

•使用 yield from 語法,等待另外一個協程的返回;

•使用 asyncio.sleep() 代替 time.sleep(),其中 time.sleep() 阻塞,asyncio.sleep() 非阻塞,可以切換任務。

import asyncio


@asyncio.coroutine
def task():    
print("task start")   
print("sleep...")    
yield from asyncio.sleep(10)    
print("task end")

 

main 方法與同步代碼的區別包括:

•調用 asyncio.get_event_loop() 創建事件迴圈,事件迴圈用於運行非同步任務和回調;

•調用 loop.run_until_complete() 將協程對象交給事件迴圈,並阻塞直到協程運行結束才返回;

•調用 loop.close(),關閉事件迴圈。

def main():  
  print("main start")   
   loop = asyncio.get_event_loop()    
   c = task()   
    loop.run_until_complete(c)   
     loop.close()    
     print("main end")    
     return True

 

執行結果如下所示,表明是同步執行。

main starttask startsleep…task endmain endtask_result=True

 

協程同步執行的原因是 asyncio 是一個基於事件迴圈的非同步IO模塊,其中通過 yield from 將協程的 async.sleep() 的控制權交給事件迴圈,然後掛起協程,也就是說 yield from 等待協程 async.sleep() 的返回結果。等待過程中讓出CPU執行權,由事件迴圈決定何時喚醒 async.sleep(),接著向後執行代碼。

可見,協程的作用體現在切換,切換生效的前提是存在多任務,當前代碼中只有一個任務,因此體現不出協程的作用。

4.2 多任務

如下所示創建多任務,並將協程對象交給事件迴圈。

def main():    
print("main start")   
 loop = asyncio.get_event_loop()   
  # c = task()   
   # loop.run_until_complete(c)   
    c1 = task()   
     c2 = task()   
      loop.run_until_complete(asyncio.wait([c1, c2]))    
      loop.close()   
       print("main end")  
         return True

 

執行結果如下所示,第二個任務的 task start 在第一個任務的 task end 之前執行,表明任務已切換。

main start
task start
sleep...
task start
sleep...
task end
task end
main end
task_result=True

 

同時,與協程執行單任務相同,task_result=True 也是最後執行,可見協程並不適用於函數的提前返回。

因此,可以根據場景選擇使用多線程/多進程與協程。

•多線程/多進程,適用於後臺執行,函數提前返回的場景;

•協程,適用於多任務併發執行,可以降低IO等待,最終降低多任務總的執行用時,無法降低單任務的執行用時。

當然,多線程/多進程也可以用於併發執行,需要根據具體場景選擇使用哪種方式實現。

在這裡插入圖片描述

5 結論

本文中的業務場景是實現介面的提前返回,後臺運行耗時操作。

本文中提到的非同步執行指的是後臺運行某些代碼或功能,不阻塞主程式。

非同步執行的常規實現方式是使用多線程/多進程,缺點是需要將原函數作為參數傳遞給線程/進程,因此需要修改調用的函數名。

結合裝飾器可以實現在不改變任務函數調用方式的前提下實現非同步操作。

此外,使用協程也可以實現非同步執行。但是協程適用於多任務併發處理,遇到耗時操作時自動切換任務,可以縮短多任務總的執行用時,而無法縮短單任務的執行用時,無法實現提前返回,因此不適用該場景。

事實上,Python中由於GIL的存在,在一個進程中每次只能有一個線程在運行,因此多線程處理併發的實現方式也是任務線程的切換。某個線程想要運行,首先要獲得GIL鎖,然後遇到IO或者超時的時候釋放GIL鎖,給其餘的線程去競爭,競爭成功的線程獲得GIL鎖得到下一次運行的機會。

因此,Python中多線程適用於IO密集型應用。

多進程處理併發的實現方式與CPU的核數有關。對於單核CPU,一個時間點只能運行一個進程,因此只能併發,無法並行,多進程通過時間片輪轉的方式輪流占用CPU。對於多核CPU,一個時間點每個CPU可以運行一個進程,因此可以實現並行。

因此,Python中多進程適用於CPU密集型應用。

多線程/多線程與協程的相同點是都可以實現任務的切換,不同點是切換機制的實現方式。

一個程式想要同時處理多個任務,必須提供一種能夠記錄任務執行進度的機制。多線程/多線程由CPU提供該機制,協程由事件迴圈提供。

6 小技巧

裝飾器+多線程 可用於比較優雅地實現非同步任務,不阻塞主程式。

協程的優勢在於多任務併發處理,可以實現輕量級的任務切換。

因此,使用過程中需要先確認業務場景,如果目標是提起返回,可以使用多線程,如果目標是多任務併發處理,可以使用協程。
今天分享的就那麼多,到這裡就沒有了,喜歡的點贊收藏,不懂的評論留言,一般我看見都會回覆的。下一篇見啦。

在這裡插入圖片描述


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 橋接模式是什麼 橋接模式:橋接是一種結構型設計模式, 可將業務邏輯或一個大類拆分為不同的層次結構, 從而能獨立地進行開發。 為什麼用橋接模式 對於兩個獨立變化的維度,使用橋接模式再適合不過了. 橋接模式怎麼實現 這裡是將computer和printer分成兩層,用介面的方式把強耦合轉化為弱耦合。這兩 ...
  • 我在之前一段時間做過網路通信的系列文章,但是文章還是偏散,沒有一個整體脈絡,本篇就以知識地圖的形式來進行梳理。 知識地圖是一種知識導航系統,並顯示不同的知識存儲之間重要的動態聯繫。本篇主要就是從更高的視角將之前的文章的結構思路展現出來。文章結構的思路實際上也是達到架構師程度要掌握的網路通信知識學習路 ...
  • 觀察者模式又叫做發佈-訂閱模式,屬於行為型模式;觀察者模式通過定義一種一對多得依賴關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。 觀察者模式的UML類圖如下: 如上圖所示,觀察者模式主要涉及到抽象主題角色、具體主題角色、抽 ...
  • 網路開發兩大架構 早期數據交互的格式是沒有網路的兩個文件之間的數據交互需要通過第三個文件a,b文件同時和c文件交互,a文件把數據先存放c文件中b文件從c文件取,反之亦然socket (套接字) 是一個收發數據的工具 有了網路之後a文件中的數據,通過網路協議,轉化101010...二進位進行發送a文件 ...
  • **Spring淪陷了!**這樣的標題這幾天是不是看膩了?然而,仔細看看都是拿著之前的幾個毫不相干的CVE來大吹特吹。所以,昨天發了一篇關於最近網傳的Spring大漏洞的文章,聊了聊這些讓人迷惑的營銷文、以及提醒大家不要去下載一些利用漏洞提供補丁的釣魚內容。而對於這個網傳的漏洞,依然保持關註狀態,因 ...
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
  • 函數的嵌套有兩種方式: 交叉嵌套 迴環嵌套 交叉嵌套 交叉嵌套的方式是在本函數中調用同一級或上一級函數的嵌套方法: def func(foo): print(1) foo() print(3) def a(): print(1) b = func(a) print(b) 輸出的結果為: 1 1 3 ...
  • redis的基本命令學習 1.簡單理解redis 基於記憶體的key-value資料庫基於c語言編寫的,可以支持多種語言的api //set每秒11萬次,取get 81000次支持數據持久化value可以是string,hash, list, set, sorted set 使用場景: 去最新n個數據 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...