greenlet 輕量級的併發程式設計

2022-07-15 22:21:17 字數 4289 閱讀 6296

1 關於greenlet

greelet指的是使用乙個任務排程器和一些生成器或者協程實現協作式使用者空間多執行緒的一種偽併發機制,即所謂的微執行緒。

greelet機制的主要思想是:生成器函式或者協程函式中的yield語句掛起函式的執行,直到稍後使用next()或send()操作進行恢復為止。可以使用乙個排程器迴圈在一組生成器函式之間協作多個任務。

網路框架的幾種基本的網路i/o模型:

阻塞式單執行緒:這是最基本的i/o模型,只有在處理完乙個請求之後才會處理下乙個請求。它的缺點是效能差,如果有請求阻塞住,會讓服務無法繼續接受請求。但是這種模型編寫**相對簡單,在應對訪問量不大的情況時是非常適合的。

非阻塞式事件驅動:為了解決多執行緒的問題,有一種做法是利用乙個迴圈來檢查是否有網路io的事件發生,以便決定如何來進行處理(reactor設計模式)。這樣的做的好處是進一步降低了cpu的資源消耗。缺點是這樣做會讓程式難以編寫,因為請求接受後的處理過程由reactor來決定,使得程式的執行流程難以把握。當接受到乙個請求後如果涉及到阻塞的操作,這個請求的處理就會停下來去接受另乙個請求,程式執行的流程不會像線性程式那樣直觀。twisted框架就是應用這種io模型的典型例子。

非阻塞式coroutine(協程):這個模式是為了解決事件驅動模型執行流程不直觀的問題,它在本質上也是事件驅動的,加入了coroutine的概念。

2 與執行緒/程序的區別

執行緒是搶占式的排程,多個執行緒並行執行,搶占共同的系統資源;而微執行緒是協同式的排程。

其實greenlet不是一種真正的併發機制,而是在同一執行緒內,在不同函式的執行**塊之間切換,實施「你執行一會、我執行一會」,並且在進行切換時必須指定何時切換以及切換到哪。greenlet的介面是比較簡單易用的,但是使用greenlet時的思考方式與其他併發方案存在一定區別:

1. 執行緒/程序模型在大邏輯上通常從併發角度開始考慮,把能夠並行處理的並且值得並行處理的任務分離出來,在不同的執行緒/程序下執行,然後考慮分離過程可能造成哪些互斥、衝突問題,將互斥的資源加鎖保護來保證併發處理的正確性。

2. greenlet則是要求從避免阻塞的角度來進行開發,當出現阻塞時,就顯式切換到另一段沒有被阻塞的**段執行,直到原先的阻塞狀況消失以後,再人工切換回原來的**段繼續處理。因此,greenlet本質是一種合理安排了的序列

3. greenlet本質是序列,因此在沒有進行顯式切換時,**的其他部分是無法被執行到的,如果要避免**長時間占用運算資源造成程式假死,那麼還是要將greenlet與執行緒/程序機制結合使用(每個執行緒、程序下都可以建立多個greenlet,但是跨執行緒/程序時greenlet之間無法切換或通訊)。

3 使用

乙個 「greenlet」 是乙個很小的獨立微執行緒。可以把它想像成乙個堆疊幀,棧底是初始呼叫,而棧頂是當前greenlet的暫停位置。你使用greenlet建立一堆這樣的堆疊,然後在他們之間跳轉執行。跳轉不是絕對的:乙個greenlet必須選擇跳轉到選擇好的另乙個greenlet,這會讓前乙個掛起,而後乙個恢復。兩 個greenlet之間的跳轉稱為切換(switch)

當你建立乙個greenlet,它得到乙個初始化過的空堆疊;當你第一次切換到它,他會啟動指定的函式,然後切換跳出greenlet。當最終棧底 函式結束時,greenlet的堆疊又程式設計空的了,而greenlet也就死掉了。greenlet也會因為乙個未捕捉的異常死掉。

示例:來自官方文件示例

from greenlet import greenlet 

def test1(): 

print 12 

gr2.switch() 

print 34 

def test2(): 

print 56 

gr1.switch() 

print 78 

gr1 = greenlet(test1) 

gr2 = greenlet(test2) 

gr1.switch()

最後一行跳轉到 test1() ,它列印12,然後跳轉到 test2() ,列印56,然後跳轉回 test1() ,列印34,然後 test1() 就結束,gr1死掉。這時執行會回到原來的 gr1.switch() 呼叫。注意,78是不會被列印的,因為gr1已死,不會再切換。

4 基於greenlet的框架

4.1 eventlet

eventlet 是基於 greenlet 實現的面向網路應用的併發處理框架,提供「執行緒」池、佇列等與其他 python 執行緒、程序模型非常相似的 api,並且提供了對 python 發行版自帶庫及其他模組的超輕量併發適應性調整方法,比直接使用 greenlet 要方便得多。

其基本原理是調整 python 的 socket 呼叫,當發生阻塞時則切換到其他 greenlet 執行,這樣來保證資源的有效利用。需要注意的是:

4.2 gevent

4.2.1 gevent是乙個基於協程(coroutine)的python網路函式庫,通過使用greenlet提供了乙個在libev事件迴圈頂部的高階別併發api。

主要特性有以下幾點:

ps:1、關於linux的epoll機制:

epoll是linux核心為處理大批量檔案描述符而作了改進的poll,是linux下多路復用io介面select/poll的增強版本,它能顯著提高程式在大量併發連線中只有少量活躍的情況下的系統cpu利用率。epoll的優點:

2、libev機制

提供了指定檔案描述符事件發生時呼叫**函式的機制。libev是乙個事件迴圈器:向libev註冊感興趣的事件,比如socket可讀事件,libev會對所註冊的事件的源進行管理,並在事件發生時觸發相應的程式。

4.2.2 官方文件中的示例:

>>> import gevent

>>> from gevent import socket

>>> urls = ['www.google.com.hk','www.example.com', 'www.python.org'  ]

>>> jobs = [gevent.spawn(socket.gethostbyname, url) for url in urls]

>>> gevent.joinall(jobs, timeout=2)

>>> [job.value for job in jobs]

['74.125.128.199', '208.77.188.166', '82.94.164.162']

註解:gevent.spawn()方法spawn一些jobs,然後通過gevent.joinall將jobs加入到微執行緒執行佇列中等待其完成,設定超時為2秒。執行後的結果通過檢查gevent.greenlet.value值來收集。gevent.socket.gethostbyname()函式與標準的socket.gethotbyname()有相同的介面,但它不會阻塞整個直譯器,因此會使得其他的greenlets跟隨著無阻的請求而執行。

4.2.3 monket patching

python的執行環境允許我們在執行時修改大部分的物件,包括模組、類甚至函式。雖然這樣做會產生「隱式的***」,而且出現問題很難除錯,但在需要修改python本身的基礎行為時,monkey patching就派上用場了。monkey patching能夠使得gevent修改標準庫裡面大部分的阻塞式系統呼叫,包括socket,ssl,threading和select等模組,而變成協作式執行。

>>> from gevent import monkey ;

>>> monkey . patch_socket ()

>>> import urllib2

通過monkey.patch_socket()方法,urllib2模組可以使用在多微執行緒環境,達到與gevent共同工作的目的。

4.2.4 事件迴圈

不像其他網路庫,gevent和eventlet類似, 在乙個greenlet中隱式開始事件迴圈。沒有必須呼叫run()或dispatch()的反應器(reactor),在twisted中是有 reactor的。當gevent的api函式想阻塞時,它獲得hub例項(執行時間迴圈的greenlet),並切換過去。如果沒有集線器例項則會動態 建立。

libev提供的事件迴圈預設使用系統最快輪詢機制,設定libev_flags環境變數可指定輪詢機制。libev_flags=1為select, libev_flags = 2為poll, libev_flags = 4為epoll,libev_flags = 8為kqueue。

libev的api位於gevent.core下。注意libev api的**在hub的greenlet執行,因此使用同步greenlet的api。可以使用spawn()和event.set()等非同步api。

輕量級的web server

web介面是乙個應用系統常用的介面,本文所說的輕量級的web server是指應用系統不以web訪問為主,web介面提供輔助作用,例如,修改配置等,此時,對web server的要求是程式簡單 無或者很輕的併發 能嵌入到應用中最好。linux上nginx的安裝依賴於pcre,這是乙個與perl相容的...

輕量級的 XML ORM

開發中,經常使用xml來作資料庫,涉及到對xml的操作比較頻繁。如果每次都去一步步的寫那些過程,真的是很浪費時間。經過一段時間的總結,我將xml的常用操作封裝成乙個dll,實現了對xml的增刪改查。對一般的xml開發已經夠用了。說是orm其實也有些勉強,不過操作起來還是比較方便的。下面我將演示怎麼利...

Java併發 偏向鎖 輕量級鎖 重量級鎖

鎖的狀態一共有四種 無鎖 偏向鎖 輕量級鎖 重量級鎖 鎖的狀態儲存在物件的標頭檔案中,以32位jdk為例 鎖狀態25 bit 4bit 1bit 2bit 23bit 2bit 是否是偏向鎖 鎖標誌位 輕量級鎖 指向棧中鎖記錄的指標 00重量級鎖 指向互斥量 重量級鎖 的指標 10gc標記空11 偏...