Werkzeug庫——local模組

發表於2017-05-18

一、簡介

local模組中,Werkzeug實現了類似Python標準庫中thread.local的功能。thread.local是執行緒區域性變數,也就是每個執行緒的私有變數,具有執行緒隔離性,可以通過執行緒安全的方式獲取或者改變執行緒中的變數。參照thread.local,Werkzeug實現了比thread.local更多的功能。Werkzeug官方文件關於local模組中對此進行了說明:

The Python standard library comes with a utility called “thread locals”. A thread local is a global object in which you can put stuff in and get back later in a thread-safe way. That means whenever you set or get an object on a thread local object, the thread local object checks in which thread you are and retrieves the correct value.

This, however, has a few disadvantages. For example, besides threads there are other ways to handle concurrency in Python. A very popular approach is greenlets. Also, whether every request gets its own thread is not guaranteed in WSGI. It could be that a request is reusing a thread from before, and hence data is left in the thread local object.

總結起來: 以上文件解釋了對於“併發”問題,多執行緒並不是唯一的方式,在Python中還有“協程”(關於協程的概念和用法可以參考:廖雪峰的部落格)。“協程”的一個顯著特點在於是一個執行緒執行,一個執行緒可以存在多個協程。也可以理解為:協程會複用執行緒。對於WSGI應用來說,如果每一個執行緒處理一個請求,那麼thread.local完全可以處理,但是如果每一個協程處理一個請求,那麼一個執行緒中就存在多個請求,用thread.local變數處理起來會造成多個請求間資料的相互干擾。

對於上面問題,Werkzeug庫解決的辦法是local模組。local模組實現了四個類:

  • Local
  • LocalStack
  • LocalProxy
  • LocalManager

本文重點介紹前兩個類的實現。

二、Local類

Local類能夠用來儲存執行緒的私有變數。在功能上這個thread.local類似。與之不同的是,Local類支援Python的協程。在Werkzeug庫的local模組中,Local類實現了一種資料結構,用來儲存執行緒的私有變數,對於其具體形式,可以參考它的建構函式:

從上面類定義可以看出,Local類具有兩個屬性:__storage____ident_func__。從建構函式來看,__storage__是一個字典,而__ident_func__是一個函式,用來識別當前執行緒或協程。

1. __ident_func__

關於當前執行緒或協程的識別,local模組引入get_ident函式。如果支援協程,則從greenlet庫中匯入相關函式,否則從thread庫中匯入相關函式。呼叫get_ident將返回一個整數,這個整數可以確定當前執行緒或者協程。

2. __storage__

__storage__是一個字典,用來儲存不同的執行緒/協程,以及這些執行緒/協程中的變數。以下是一個簡單的多執行緒的例子,用來說明__storage__的具體結構。

上面的例子,具體分析為:

  • 首先,程式碼建立了一個Local的例項l,並且訪問它的__storage__屬性。由於目前還沒有資料,所以l.__storage__的結果為{};
  • 程式碼建立了3個執行緒,每個執行緒均執行add_arg(arg, i)函式。這個函式會為每個執行緒建立一個變數,並對其賦值;
  • 最後,再次訪問l.__storage__。這次,l例項中將包含3個執行緒的資訊。其結果為:

從以上結果可以看出,__storage__這個字典的鍵表示不同的執行緒(通過get_ident函式獲得執行緒標識數值),而值表示對應執行緒中的變數。這種結構將不同的執行緒分離開來。當某個執行緒要訪問該執行緒的變數時,便可以通過get_ident函式獲得執行緒標識數值,進而可以在字典中獲得該鍵對應的值資訊了。

三、LocalStack類

LocalStack類和Local類類似,但是它實現了棧資料結構。

LocalStack類初始化的時候,便會建立一個Local例項,這個例項用於儲存執行緒/協程的變數。與此同時,LocalStack類還實現了pushpoptop等方法或屬性。呼叫這些屬性或者方法時,該類會根據當前執行緒或協程的標識數值,在Local例項中對相應的數值進行操作。以下還是以一個多執行緒的例子進行說明:

 

以上例子的具體分析過程如下:

  • 首先,先建立一個LocalStack例項_stack,這個例項將儲存執行緒/協程的變數資訊;
  • 在程式開始執行時,先檢查_stack中包含的資訊;
  • 之後建立兩個執行緒,分別執行worker函式;
  • worker函式首先會產生一個上下文物件,這個上下文物件會放入_stack中。在這個上下文環境中,程式執行一些操作,列印一些資料。當退出上下文環境時,_stack會pop該上下文物件。
  • 在程式結束時,再次檢查_stack中包含的資訊。

執行上面的測試例子,產生結果如下:

注意到:

  • 當兩個執行緒在執行時,_stack中會儲存這兩個執行緒的資訊,每個執行緒的資訊都儲存在類似{'stack': [a1, b1, c1]}的結構中(注:stack鍵對應的是放入該棧中的物件,此處為了方便列印了該物件的一些屬性)。
  • 當執行緒在休眠和執行中切換時,通過執行緒的標識數值進行區分不同執行緒,執行緒1執行時它通過標識數值只會對屬於該執行緒的數值進行操作,而不會和執行緒2的數值混淆,這樣便起到執行緒隔離的效果(而不是通過鎖的方式)。
  • 由於是在一個上下文環境中執行,當執行緒執行完畢時,_stack會將該執行緒儲存的資訊刪除掉。在上面的執行結果中可以看出,當執行緒2執行結束後,_stack中只包含執行緒1的相關資訊。當所有執行緒都執行結束,_stack的最終狀態將為空。

相關文章