OpenStack公共組件oslo之十——oslo.concurrency

        oslo.concurrency是一個爲OpenStack其他項目提供用於管理線程的工具庫,這樣,OpenStack其他項目可以直接調用oslo.concurrency庫利用其鎖機制安全的運行多線程和多進程應用,也可以運行外部進程。本文總結了oslo.concurrency中常用的工具類或方法及其對應的使用方法。

1. lockutils

        lockutils模塊封裝了oslo庫的鎖機制,其中,定義了讀寫鎖、信號量以及同步裝飾器方法等。本節分別介紹這些類和方法的實現與使用。

1.1 鎖機制

        lockutils中的鎖機制實質上是直接使用了fasteners的實現,所以本節直接介紹fasteners庫中的讀寫鎖和共享內存,即ReaderWriteerLock和InterProcessLock類。

1.1.1 ReaderWriterLock類

        ReaderWriterLock類實現了一個讀寫鎖,讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。fasteners通過ReadWriterLock類實現了讀鎖和寫鎖,其在該類中定義了READER和WRITER標識分別表示申請的鎖是讀鎖還是寫鎖。使用該類可以獲得多個讀鎖,但只能存在一個寫鎖。在目前的版本中,該類不能實現從讀寫升級到寫鎖,且寫鎖在加鎖時不能獲取讀鎖;而以後可能會對這些問題進行優化。該類的主要方法如下:

  • read_lock():爲當前線程申請一個讀鎖,其只有在沒有爲其他線程分配寫鎖時才能獲取成功;如果另一個線程以及獲取了寫鎖,調用該方法會返回RuntimeError異常。
  • write_lock():爲當前線程申請一個寫鎖,其只有在沒有爲其他線程分配讀鎖或寫鎖時才能獲取成功;一旦申請寫鎖成功,將會阻塞申請讀鎖的線程。
  • is_reader():判斷當前線程是否申請了讀鎖。
  • is_writer():判斷當前線程是否申請了寫鎖,或已申請但尚未獲得寫鎖。
  • owner():判斷當前線程是否申請了鎖;如果獲得了鎖是讀鎖還是寫鎖。
        ReaderWriterLock類中包含_writer屬性表示佔有寫鎖的線程,_readers屬性表示佔有讀鎖的線程集合,_pending_writers屬性表示正在等待分配寫鎖的線程的隊列,_current_thread屬性表示當前線程,_cond屬性表示threading.Condition對象。上述的這些方法就是通過操作這幾個屬性實現讀寫鎖的。

1.2.1 InterProcessLock類

        InterProcessLock類是一個在POSIX系統上工作的進程間鎖機制的實現類。該類會通過當前操作系統確定其內部的實現機制,通過該類可以實現多進程應用中共享內存、進程間通信與同步以及加鎖等操作。其主要實現了tryLock()方法實現加鎖,unlock()方法實現解鎖,對於其具體實現,由於操作系統的不同其實現方法也不同,本人能力有限不再進行深入解析,有興趣的同學可以自行研究。

1.2 信號量

        lockutils中也實現了信號量,準確來說是一個信號量垃圾收集的容器。這個集合在內部使用一個字典,這樣當一個信號量不再被任何線程使用時,它將被垃圾收集器自動從這個容器中移除。其提供了一個get(name)方法,可以通過名稱獲取一個信號量。在具體使用時,可以直接lockutils中的internal_lock(name, semaphores=None)獲取這個信號量容器。

1.3 同步裝飾器

        lockutils中定義了兩個同步裝飾器方法synchronized(name, lock_file_prefix=None, external=False, lock_path=None, semaphores=None, delay=0.01)和synchronized_with_prefix(lock_file_prefix)。前者直接使用@synchronized(name)對裝飾的方法加同步鎖;而後者可以通過重新定義使用@synchronized(name)對裝飾的方法加一個帶有前綴的同步鎖。

1.4 外部鎖

        lockutils中也定義了兩個方法分別用來獲取和刪除外部鎖:external_lock(name, lock_file_prefix=None, lock_path=None)和remove_external_lock_file(name, lock_file_prefix=None, lock_path=None, semaphores=None)。其需要指定鎖文件的前綴、鎖文件路徑以及鎖的名稱,通過這些屬性,lockutils可以通過_get_lock_path(name, lock_file_prefix, lock_path=None)方法獲取鎖的位置,並根據鎖文件創建和刪除一個外部鎖。

2 processutils

        processutils模塊定義了一系列系統級的工具類和輔助函數。本小節將介紹processutils庫的相關類或方法。

2.1 進程或線程異常類

        processutils模塊中定義了多個進程或線程異常類,如參數不合法異常InvalidArgumentError、不知名參數異常UnknownArgumentError、進程執行異常ProcessExecutionError、日誌記錄異常LogErrors等。

2.2 資源限制類

        ProcessLimits類封裝了一個進程對資源的限制,這些限制主要包括以下幾個方面:

  • address_space:進程地址空間限制。
  • core_file_size:core文件大小限制。
  • cpu_time:CPU執行當前進程時間限制。
  • data_size:數據大小限制。
  • file_size:文件大小限制。
  • memory_locked:加鎖的內存大小限制。
  • number_files:打開的文件最大數量限制。
  • number_processes:進程的最大數量限制。
  • resident_set_size:最大駐留集(RSS)大小限制。
  • stack_size:棧大小限制。

2.3 進程執行方法

        processutils模塊中定義了執行進程的方法等,主要方法包括以下幾個:
  • execute()方法:該方法通過啓動一個子進程提取並執行一個命令。主要參數有:待執行的命令cmd;設置當前目錄的cwd;發送到打開的進程process_input;爲進程設置環境變量的env_variables;代表退出進程的int、bool或list值check_exit_code,默認爲0,只有產生異常纔會設置爲其他值;重試延遲時間delay_on_retry,如果設置爲True,表示馬上進行重試操作;cmd重試次數attempts;run_as_root,該值如果設置爲True,則爲cmd命令加上root_helper指定的前綴;爲命令指定的前綴root_helper;shell表示是否使用shell執行這個命令;執行命令記錄日誌的等級loglevel;監聽錯誤日誌log_errors,是一個LogErrors對象;binary,該值如果爲True,則返回Unicode編碼的stdout後stderr;prlimit表示一個ProcessLimits對象,用於限制執行該cmd的命令的資源用量。
  • trycmd()方法:execute()的一個裝飾器,使用這個裝飾器可以更加容易的處理錯誤和異常。返回一個包含命令輸出strdout或stderr字符串的元組。如果err不爲空,則表示執行命令出現異常或錯誤。
  • ssh_execute():通過ssh執行命令。
  • get_worker_count():獲取默認的worker數量,返回CPU的數量;如果無法確定則返回1.

3 watchdog

        watchdog模塊實現了一個看門狗程序,定義了一個watch()方法。如果操作執行時間超過閾值,則記錄日誌;此時可能發生了死鎖或是一個耗時操作。其包含四個參數:
  • logger:一個記錄日誌的對象。
  • action:描述將執行的操作。
  • level:表示記錄日誌的等級,默認爲debug。
  • after:發送消息之前的持續時間,默認爲5s。

4 使用方法

        上文介紹了oslo.concurrency的各個模塊的實現,接下來將詳細介紹如何使用這些模塊更好的管理OpenStack項目的線程或進程。
from oslo_concurrency import lockutils

@synchronized('mylock')
def foo(self, *args):
    ...

@synchronized('mylock')
def bar(self, *args):
    ...
        爲一個方法添加@syschronized裝飾器,可以保證統一時刻只有一個線程執行這個方法;但是,同時可以有兩個方法共享這個鎖,此時統一時刻要麼只能執行foo方法,要麼只能執行bar方法。
(in nova/utils.py)
from oslo_concurrency import lockutils

synchronized = lockutils.synchronized_with_prefix('nova-')


(in nova/foo.py)
from nova import utils

@utils.synchronized('mylock')
def bar(self, *args):
    ...
        如果需要設置一個帶有前綴的同步鎖,可以使用如上的方式進行設置。
        FORMAT = '%(asctime)-15s %(message)s'
        logging.basicConfig(format=FORMAT)
        LOG = logging.getLogger('mylogger')

        with watchdog.watch(LOG, "subprocess call", logging.ERROR):
            subprocess.call("sleep 10", shell=True)
            print "done"
        如果設置一個看門狗,則可以使用with語法調用watchdog.watch()方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章