python---協程 學習筆記

来源:https://www.cnblogs.com/charles8866/archive/2018/02/13/8445991.html
-Advertisement-
Play Games

協程 協程又稱為微線程,協程是一種用戶態的輕量級線程 協程擁有自己的寄存器和棧。協程調度切換的時候,將寄存器上下文和棧都保存到其他地方,在切換回來的時候,恢復到先前保存的寄存器上下文和棧,因此:協程能保留上一次調用狀態,每次過程重入時,就相當於進入上一次調用的狀態。 協程的好處: 1.無需線程上下文 ...


協程


協程又稱為微線程,協程是一種用戶態的輕量級線程

協程擁有自己的寄存器和棧。協程調度切換的時候,將寄存器上下文和棧都保存到其他地方,在切換回來的時候,恢復到先前保存的寄存器上下文和棧,因此:協程能保留上一次調用狀態,每次過程重入時,就相當於進入上一次調用的狀態。

協程的好處:

1.無需線程上下文切換的開銷(還是單線程)

2.無需原子操作(一個線程改一個變數,改一個變數的過程就可以稱為原子操作)的鎖定和同步的開銷

3.方便切換控制流,簡化編程模型

4.高併發+高擴展+低成本:一個cpu支持上萬的協程都沒有問題,適合用於高併發處理

缺點:

1.無法利用多核的資源,協程本身是個單線程,它不能同時將單個cpu的多核用上,協程需要和進程配合才能運用到多cpu上(協程是跑線上程上的)

2.進行阻塞操作時會阻塞掉整個程式:如io

使用yield實現協程的例子:

import time
import queue
def consumer(name):
    print("---->start eating baozi......")
    while True:
        #yield預設可以返回數據,走到yield整個程式返回,yield被喚醒的時候還可以接收數據
        #進入死迴圈碰到yield暫停,被喚醒的時候才執行print
        new_baozi = yield
        print("[%s] is eating baozi %s" %(name,new_baozi))
        #time.sleep(2)
def producer():
    #__next__()調用消費者的next,consumer直接調用的話,第一次不會執行,會變成一個生成器
    #函數如果裡面有yield第一次加括弧調用,他是一個生成器,還沒有真正執行,__next__()才會執行
    r = con.__next__()
    r = con2.__next__()
    n = 0
    while n<5:
        n += 1
        #send有兩個作用:喚醒生成器的同時,傳送一個值,傳的這個值就是yield接收到的值new_baozi
        con.send(n)
        con2.send(n)
     #time.sleep(1)
print("\033[32;1m[producer]\033[0m is making baozi %s" %n) if __name__ == '__main__': con = consumer("c1") con2 = consumer("c2") p = producer()

 

整個就是通過yield實現的一個簡單的協程,在程式運行過程中,直接完成運作,感覺像多併發的效果。

問題:他們好像能夠實現多併發的效果,是因為每一個生產者沒有任何的sleep,如果在producer中加一個time.sleep(1),那麼運行速度就變慢了。

遇到io操作就切換,io操作很耗時,協程之所以能夠處理大併發,就是把io操作去除,之後就變成這個程式只有cpu在切換

如何實現程式自動檢測io操作完成:

greenlet

from greenlet import greenlet
def tesst1():
    print(12)
    gr2.switch()
    print(34)
    gr2.switch()
def tesst2():
    print(56)
    gr1.switch()
    print(78)
gr1 = greenlet(tesst1)#啟動一個協程
gr2 = greenlet(tesst2)
gr1.switch()#手動切換

運行結果:

greenlet現在還是手動切換

gevent

Gevent是一個第三方庫,可以輕鬆通過gevent實現併發同步或非同步編程,在gevent中用到的主要模式是Greenlet,它是以c擴展模塊形式接入python的輕量級協程。Greenlet全部運行在主程式操作系統進程的內部,但他們被協作式的調度

import gevent
def foo():
    print("Running in foo")
    gevent.sleep(2)
    print("Explicit context switch to foo again")

def bar():
    print("Explicit context to bar")
    gevent.sleep(1)
    print("Implicit context to switch back to bar")

gevent.joinall(
    [
        gevent.spawn(foo),
        gevent.spawn(bar),
    ]
)

運行結果:

運行整個也就運行了2秒,模擬io操作

下麵我們做一個簡單的小爬蟲:

from urllib.request import urlopen
import gevent,time
def f(url):
    print("GET: %s "%url)
    resp = urlopen(url)
    data = resp.read()
    print("%d bytes received from %s." %(len(data),url))
urls = [
    'https://www.python.org/',
    'https://www.yahoo.com/',
    'https://github.com/'
]
time_start = time.time()
for url in urls:
    f(url)
print("同步cost",time.time()-time_start)

async_time_start = time.time()

gevent.joinall([
    gevent.spawn(f,'https://www.python.org/'),
    gevent.spawn(f,'https://www.yahoo.com/'),
    gevent.spawn(f,'https://github.com/')
])
print("非同步cost",time.time()-time_start)

 

用了同步和非同步兩種方式來進行網頁的爬取,但是結果卻是同步的比非同步的用的時間還要短,我們之前用了gevent.sleep(1)來模擬io操作,順便就完成了執行任務。原因是:gevent調用urllib預設是堵塞的,gevent檢測不到urllib的io操作,所以不會進行切換,所以還是串列運行。

那怎麼才能夠讓gevent知道urllib是io操作呢,要用到一個模塊monkey

from urllib.request import urlopen
import gevent,time
from gevent import monkey
monkey.patch_all()#把當前程式的所有io操作單獨的坐上標記
def f(url):
    print("GET: %s "%url)
    resp = urlopen(url)
    data = resp.read()
    print("%d bytes received from %s." %(len(data),url))
urls = [
    'https://www.python.org/',
    'https://www.yahoo.com/',
    'https://github.com/'
]
time_start = time.time()
for url in urls:
    f(url)
print("同步cost",time.time()-time_start)

async_time_start = time.time()

gevent.joinall([
    gevent.spawn(f,'https://www.python.org/'),
    gevent.spawn(f,'https://www.yahoo.com/'),
    gevent.spawn(f,'https://github.com/')
])
print("非同步cost",time.time()-async_time_start)

這樣就非同步爬取的速度瞬間快了很多

下麵我們在利用gevent寫一個socket伺服器端和客戶端:

伺服器端:

 1 import sys,socket,time,gevent
 2 
 3 from gevent import socket,monkey
 4 monkey.patch_all()
 5 
 6 def server(port):
 7     s = socket.socket()
 8     s.bind(('0.0.0.0',port))
 9     s.listen(500)
10     while True:
11         cli,addr = s.accept()
12         gevent.spawn(handle_request,cli)
13 def handle_request(conn):
14     try:
15         while True:
16             data = conn.recv(1024)
17             print("recv:",data)
18             conn.send(data)
19             if not data:
20                 conn.shutdown(socket.SHUT_WR)
21     except Exception as e:
22         print(e)
23     finally:
24         conn.close()
25 
26 if __name__ == '__main__':
27     server(8001)

 

和socketserver差不多,處理數據都是在handle_request函數中,然後在server中建立一個gevent

客戶端:

 1 import socket
 2 HOST = 'localhost'
 3 PORT = 8001
 4 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 s.connect((HOST,PORT))
 6 while True:
 7     msg = bytes(input(">>>:"),encoding="utf-8")
 8     s.sendall(msg)
 9     data = s.recv(1024)
10     #repr格式化輸出
11     print("Recv:",repr(data))
12 s.close()

 

運行結果:

由上面幾個運行結果可以知道:我們已經實行了併發運行,不需要使用什麼多線程。我們用協程,遇到io就阻塞

 

我們現在實現了切換,但是我們是什麼時候切換回來呢,我們怎麼知道什麼時候這個函數的東西執行完,切換到原函數呢

事件驅動與非同步IO

通常,我們寫伺服器處理模型的程式時,有一下幾個模型:

1.每收到一個請求,創建一個新的進程,來處理改請求:如socketserver

2.每收到一個請求,創建一個新的線程,來處理該請求:如socketserver

3.每收到一個請求,放入一個事件列表,讓主進程通過非阻塞I/O方式來處理請求,即事件驅動的模式,該模式是大多數網路伺服器採用的方式

在ui編程中,我們常常要對滑鼠點擊進行相應的反應,我們怎麼獲得滑鼠點擊呢?

目前大部分的UI編程都是事件驅動模型,如很多的ui平臺都會提供onclick()事件,這個事件就代表了滑鼠按下的事件。事件驅動模型大體思路:

1.有一個事件隊列

2.滑鼠按下時,往這個隊列中增加一個點擊事件

3.有個迴圈,不斷的從隊列中取出事件,根據不同的事件,調用不同 的函數,如:onclick(),onkeydown()等

4.事件(消息)一般都各自保存各自的處理函數指針,這樣,每個消息都有獨立的處理函數

那我們在回到上面的問題,io什麼時候切換回來。我們可以註冊一個回調函數,回調函數就是當你的程式一遇到io操作,就切換,然後等著io操作結束又切換回來。io操作是操作系統完成的。就是通過這個事件驅動。

 

 

寫的不是太好,希望大家多多包涵QAQ

 


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

-Advertisement-
Play Games
更多相關文章
  • 瀏覽器引擎預設:webkit內核 一、輸入地址 當我們開始在瀏覽器中輸入網址的時候,現代瀏覽器就可以智能的匹配完整 url了,它會從歷史記錄,書簽等地方,找到已經輸入的字元串可能對應的 url,然後給出智能提示,讓你可以補全url地址。 如果輸入的地址和候選項很匹配,在敲下回車之前,chromium ...
  • 1.需要用到的方法: Math.random:取大於等於0到小於1之間的隨機數; Math.floor:向下取整; Math.ceil:向上取整; 2.看了一個面試題,要求有個函數fn,參數為n,需要返回[2,32]之間的n個整數。第一次寫的時候如下: 然後發現2到32包含2和32的時候是有31位數 ...
  • 在移動端 H5 頁面開發中,我使用了 fixed 固定某個元素在屏幕的最下方, 這時點擊輸入框,接著非常非常自然地出現了元素被系統鍵盤頂起來的情況,如下圖。 解決方案: 首先,給頁面最外層包裹一層 div(相對定位) ,然後頁面渲染完成時給 div 的高度等於 body(document.body. ...
  • 三欄佈局左右固定,中間自適應是網頁中常用到的,實現這種佈局的方式有很多種,這裡我主要寫五種。他們分別是浮動、定位、表格、flexBox、網格。 在這裡也感謝一些老師在網上發的免費教程,使我們學習起來更方便!!! 先讓大家看一下頁面效果圖: 代碼我也給寫了註釋了,這個是很基礎的一些知識點,下麵就是他們 ...
  • 一、前言 上一章學習完了Js的一些基本內容,本章開始學習JQuery語法。JQuery的基礎語法是: 那麼重要的兩個元素,一個是選擇器,另一個是行為!本章開始學習JQuery的選擇器。 二、內容 ...
  • 在前端的開發過程中,免不了進行各種調試和測試。 在不同的平臺,不同的環境下的調試方法也不盡相同,這個系列文章將探索常見的一些前端調試場景,較為系統地整理出一些調試方法。 主要包含在 PC上的 IE、FireFox、Chrome、Safari、Edge瀏覽器開發工具調試,遠程真機 安卓微信頁面、安卓常 ...
  • 一、前言 這章非常重要,由於之後需要負責平臺手機APP的日後維護,如何讓用戶在離線狀態下正常使用,以及聯網後的數據合併變得非常重要。 二、內容 離線檢測 數據存儲 ...
  • Factory負責處理生命周期的開始,而Repository幫助管理生命周期的中間和結束。 通俗的來說,Factory用於創建一個對象的新的實例,而Repository用於從資料庫中查找數據。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...