Python greenlet使用介紹及實作原理分析

高洛峰
發布: 2017-03-23 15:55:23
原創
1080 人瀏覽過

最近開始研究Python的平行開發技術,包括多線程,多進程,協程等。逐步整理了網路上的一些資料,今天整理了一下greenlet相關的資料。

並發處理的技術背景

並行化處理目前很受重視, 因為在很多時候,並行運算能大大的提高系統吞吐量,尤其在現在多核心多處理器的時代,所以像lisp這種古老的語言又被人們重新拿了起來, 函數式程式設計也越來越流行。 介紹一個python的並行處理的一個庫: greenlet。 python 有一個很有名的函式庫叫做 stackless ,用來做並發處理, 主要是弄了個叫做tasklet的微線程的東西, 而greenlet 跟stackless的最大區別是, 他很輕量級?不夠, 最大的差別是greenlet需要你自己來處理線程切換, 就是說,你需要自己指定現在要執行哪個greenlet再執行哪個greenlet。

greenlet的實作機制

以前使用python開發web程式,一直使用的是fastcgi模式.然後每個進程中啟動多個執行緒來進行請求處理.這裡有一個問題就是需要保證每個請求回應時間都要特別短,不然只要多請求幾次慢的就會讓伺服器拒絕服務,因為沒有線程能夠響應請求了.平時我們的服務上線都會進行性能測試的,所以正常情況沒有太大問題.但是不可能所有場景都測試到.一旦出現就會讓用戶等好久沒有響應.部分不可用導致全部不可用.後來轉換到了coroutine,python 下的greenlet.所以對它的實現機製做了一個簡單的了解.

每個greenlet都只是heap中的一個python object(PyGreenlet).所以對於一個進程你創建百萬甚至千萬個greenlet都沒有問題.

typedef struct _greenlet {
	PyObject_HEAD
	char* stack_start;
	char* stack_stop;
	char* stack_copy;
	intptr_t stack_saved;
	struct _greenlet* stack_prev;
	struct _greenlet* parent;
	PyObject* run_info;
	struct _frame* top_frame;
	int recursion_depth;
	PyObject* weakreflist;
	PyObject* exc_type;
	PyObject* exc_value;
	PyObject* exc_traceback;
	PyObject* dict;
} PyGreenlet;
登入後複製

每一個greenlet其實就是一個函數,以及保存這個函數執行時的上下文.對於函數來說上下文也就是其stack..同一個進程的所有的greenlets共用一個共同的操作系統分配的用戶棧.所以同一時刻只能有堆疊資料不衝突的greenlet使用這個全域的棧.greenlet是透過stack_stop,stack_start來保存其stack的棧底和棧頂的,如果出現將要執行的greenlet的stack_stop和目前棧中的greenlet重疊的情況,就要把這些重疊的greenlet的棧中資料暫時保存到heap中.保存的位置通過stack_copy和stack_saved來記錄,以便恢復的時候從heap中拷貝回棧中stack_stop和stack_start的位置.不然就會出現其棧數據會被破壞的情況.所以應用程式創建的這些greenlet就是透過不斷的拷貝資料到heap中或從heap中拷貝到棧中來實現並發的.對於io型的應用程式使用coroutine真的非常舒服.

下面是greenlet的一個簡單的棧空間模型(from greenlet.c)

A PyGreenlet is a range of C stack addresses that must be
saved and restored in such a way that the full range of the
stack contains valid data when we switch to it.

Stack layout for a greenlet:

               |     ^^^       |
               |  older data   |
               |               |
  stack_stop . |_______________|
        .      |               |
        .      | greenlet data |
        .      |   in stack    |
        .    * |_______________| . .  _____________  stack_copy + stack_saved
        .      |               |     |             |
        .      |     data      |     |greenlet data|
        .      |   unrelated   |     |    saved    |
        .      |      to       |     |   in heap   |
 stack_start . |     this      | . . |_____________| stack_copy
               |   greenlet    |
               |               |
               |  newer data   |
               |     vvv       |
登入後複製

下面是一段簡單的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()
登入後複製

目前所討論的協程,一般是程式語言提供支援的。目前我所知提供協程支援的語言包括python,lua,go,erlang, scala和rust。協程不同於執行緒的地方在於協程不是作業系統進行切換,而是由程式設計師編碼進行切換的,也就是說切換是由程式設計師控制的,這樣就沒有了執行緒所謂的安全問題。

所有的協程都共用整個行程的上下文,這樣協程間的交換也非常方便。

相對於第二種方案(I/O多路復用),使得使用協程寫的程序將更加的直觀,而不是將一個完整的流程拆分成多個管理的事件處理。協程的缺點可能是無法利用多核心優勢,不過,這個可以用協程+流程的方式來解決。

協程可以用來處理並發來提高效能,也可以用來實現狀態機來簡化程式設計。我用的更多的是第二個。去年年底接觸python,了解到了python的協程概念,後來透過pycon china2011接觸到處理yield,greenlet也是一個協程方案,而且在我看來是更可用的一個方案,特別是用來處理狀態機。

目前這一塊已經基本完成,後面抽空總結一下。

總結一下:

1)多進程能夠利用多核心優勢,但是進程間通訊比較麻煩,另外,進程數目的增加會使效能下降,進程切換的成本較高。程式流程複雜度相對I/O多重化要低。

2)I/O多重化是在一個行程內部處理多個邏輯流程,不用進行行程切換,效能較高,另外流程間共享資訊簡單。但是無法利用多核心優勢,另外,程式流程被事件處理切割成一個小塊,程式比較複雜,難於理解。

3)執行緒運行在一個行程內部,由作業系統調度,切換成本較低,另外,他們共享行程的虛擬位址空間,執行緒間共享資訊簡單。但是線程安全性問題導致線程學習曲線陡峭,而且易出錯。

4)協程有程式語言提供,由程式設計師控制進行切換,所以沒有執行緒安全性問題,可以用來處理狀態機,並發請求等。但是無法利用多核心優勢。

上面的四種方案可以搭配使用,我比較看好的是行程+協程的模式

#

以上是Python greenlet使用介紹及實作原理分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!