Python:線程、進程與協程(5)——multiprocessing模塊(2)

  上篇博文介紹了Python的multiprocessing模塊創建進程Process 類,進程間通信,進程間的同步三個部分,下面接着介紹學習進程共享。

    (1)內存共享

        在多進程情況下,由於每個進程有自己獨立的內存空間,怎樣能實現內存共享呢?multiprocessing模塊提供了Value, Array,這兩個是函數,詳細定義在sharedctypes.py裏,有興趣的可以去看看(等了解了ctypes模塊後回頭再分享下我的理解,今天就先放放)

    Value

        Value的初始化非常簡單,直接類似Value('d', 0.0)即可,具體構造方法如下:

  multiprocessing.Value(typecode_or_type, *args[,lock])。

  返回從共享內存中分配的一個ctypes 對象,其中typecode_or_type定義了返回的類型。它要麼是一個ctypes類型,要麼是一個代表ctypes類型的code。

  ctypes是Python的一個外部函數庫,它提供了和C語言兼任的數據類型,可以調用DLLs或者共享庫的函數,能被用作在python中包裹這些庫。

  *args是傳遞給ctypes的構造參數

    對於共享整數或者單個字符,初始化比較簡單,參照下圖映射關係:

Type CodeC TypePython Type
'c'charcharacter
'b'signed charint
'B'unsigned charint
'u'
Py_UNICODEunicode character
'h'signed shortint
'H'
unsigned shortint
'i'signed intint
'I'unsigned intint
'l'signed longint
'L'unsigned longint
'f'floatfloat
'd'doublefloat

比如整數1,可用Value('h',1)


    如果共享的是字符串,則在上表是找不到映射關係的,就是沒有對應的Type code可用。所以我們需要使用原始的ctype類型,對應關係如下:


ctypes typeC typePython type

c_bool

_Boolbool (1)
char char1-character string
c_wcharwchar_t1-character unicode string
c_bytecharint/long
c_ubyteunsigned charint/long
c_shortshortint/long
c_ushortunsigned shortint/long
c_intintint/long
c_uintunsigned inint/long
c_longlongint/long
c_ulongunsigned longint/long
c_longlong__int64 or long longint/long
c_ulonglongunsigned __int64 or unsigned long longint/long
c_floatfloatfloat
c_doubledoublefloat
c_longdoublelong doublefloat
c_char_pchar * (NUL terminated)string or None

c_wchar_p

wchar_t * (NUL terminated)unicode or None
c_void_pvoid *int/long or None

比如上面的Value('h',1)也可以用Value(c_short,1),字符串的話,可以用Value(c_char_p,"hello"),很好理解的。

它返回的是個對象,所以,它也有一些屬性和方法,而返回的對象是基於SynchronizedBase類,該類的定義如下:

class SynchronizedBase(object):

    def __init__(self, obj, lock=None):
        self._obj = obj
        self._lock = lock or RLock()
        self.acquire = self._lock.acquire
        self.release = self._lock.release

    def __reduce__(self):
        assert_spawning(self)
        return synchronized, (self._obj, self._lock)

    def get_obj(self):
        return self._obj

    def get_lock(self):
        return self._lock

    def __repr__(self):
        return '<%s wrapper for %s>' % (type(self).__name__, self._obj)

所以它的屬性和方法有:

value:獲取值

get_lock():獲取鎖對象

acquire/release:參考RLock對象的acquire方法,release方法,是一樣的,一個是獲取鎖,一個是釋放鎖。很好理解的。

下面舉個例子來體會一下這些方法

#coding=utf-8
import time
from multiprocessing import Value,Process
def fun(val):
    for i in range(10):
        time.sleep(0.5)
        val.value += 1

v = Value('i',0)
p_list = [Process(target=fun,args=(v,)) for i in range(10)]
for p in p_list:
    p.start()
for p in p_list:
    p.join()
print v.value

上述代碼是多個進程修改v值,我們期待它輸出的是100,但是實際上並輸出的並不是100,Value的構造函數默認的lock是True,它會創建一個鎖對象用於同步訪問控制,這就容易造成一個錯誤的意識,認爲Value在多進程中是安全的,但實際上並不是,要想真正的控制同步訪問,需要實現獲取這個鎖。所以需要修改fun()函數。如下:

def fun(val):
    for i in range(10):
        time.sleep(0.5)
        with v.get_lock():
            val.value += 1

或者如下:

def fun(val):
    for i in range(10):
        time.sleep(0.5)
        if v.acquire():
            val.value += 1
        v.release()


Array

    有了上面的基礎,這個就比較好理解了,它返回從共享內存分配的ctypes數組,原型如下:

    multiprocessing.Array(typecode_or_type, size_or_initializer, *,lock=True)

    ypecode_or_type確定返回數組的元素的類型:它是一個ctypes類型或一個字符類型代碼類型的數組模塊使用的類型。

    size_or_initializer:如果它是一個整數,那麼它確定數組的長度,並且數組將被初始化爲零。否則,size_or_initializer是用於初始化數組的序列,其長度決定數組的長度。

    如果關鍵字參數中有lock的話,lock爲True,則會創建一個新的鎖對象,以同步對該值的訪問。如果lock是Lock或RLock對象,那麼它將用於同步對該值的訪問。如果lock是False,那麼對返回的對象的訪問不會被鎖自動保護,因此它不一定是“進程安全的”。

它返回值的屬性和方法同Value差不多,有興趣的可以自己寫代碼試試,在此不舉例子。


(2)服務器進程

        通過Manager()返回的一個manager對象控制一個服務器進程,它保持住Python對象並允許其它進程使用代理操作它們。同時它用起來很方便,而且支持本地和遠程內存共享。

        Manager()返回的manager支持的類型有list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Queue, Value和Array。

    該部分的實現在managers.py文件裏,

    Manager()的定義很簡單,如下:

def Manager():
    '''
    Returns a manager associated with a running server process

    The managers methods such as `Lock()`, `Condition()` and `Queue()`
    can be used to create shared objects.
    '''
    from multiprocessing.managers import SyncManager
    m = SyncManager()
    m.start()
    return m

        它返回一個已經啓動的SyncManager對象,管理器進程將在垃圾收集或其父進程退出時立即關閉。SyncManager繼承自BaseManager。BaseManager的定義也在managers.py文件裏,有興趣的可以看看,初始化如下:BaseManager([address[, authkey]])

address:是管理器進程偵聽新連接的地址。 如果地址是無,則選擇任意一個。

authkey:是將用於檢查到服務器進程的傳入連接的有效性的認證密鑰。 如果authkey是None,那麼使用當前進程current_process()的authkey; 否則使用的authkey,它必須是字符串。

一旦創建BaseManager對象,應調用start()或get_server()。serve_forever()以確保管理器對象引用已啓動的管理器進程。

BaseManager對象的方法和屬性有:

start([initializer [,initargs]])

   啓動子過程以啓動管理器。 如果初始化程序不是None,那麼子程序在啓動時會調用initializer(*initargs)

get_server():

        返回一個Server對象,它表示在Manager控制下的實際服務器。 Server對象支持serve_forever()方法,Server對象也定義在managers.py文件裏,該類的作用用因爲解釋就是“Server class which runs in a process controlled by a manager object”,有興趣的可以去看看,瞭解下。

connect():將本地管理器對象連接到遠程管理器進程

shutdown():停止管理器在使用的進程。這僅在用start()已啓動服務器進程時可用,可以被多次調用。

register(typeid [,callable [,proxytype [,exposed [,method_to_typeid [,create_method]]]]]):

可以用於向管理器類註冊類型或可調用的類方法。

typeid是用於標識特定類型的共享對象的“類型標識符”。這必須是字符串。

callable是用於爲該類型標識符創建可調用的對象。如果將使用from_address()類方法創建管理器實例,或者如果create_method參數爲False,那麼這可以保留爲None。

proxytype是BaseProxy的子類,BaseProxy使用typeid來創建共享對象的代理。如果爲None,那麼會自動創建一個代理類。

exposed用於指定一個序列的方法名稱,該名稱可以允許使用typeid的代理對象BaseProxy的_callmethod()方法來訪問,(如果exposed爲None,則使用proxytype._exposed_,如果存在)。在沒有指定公開列表的情況下,將可以訪問共享對象的所有“公共方法”。(這裏的“公共方法”是指具有__call __()方法並且名稱不以“_”開頭的任何屬性。)

method_to_typeid是一個映射,用於指定返回代理的那些公開方法的返回類型。它將方法名映射到typeid字符串。 (如果method_to_typeid爲None,則使用proxytype._method_to_typeid_,如果存在)。如果方法的名稱不是此映射的鍵,或者映射爲None,則方法返回的對象將按值複製。

create_method確定是否應該使用名稱typeid創建一個方法,該方法可以用於告訴服務器進程創建一個新的共享對象併爲其返回一個代理。默認情況下爲True。

address:管理器使用的地址

join(timeout=None):阻塞


現在可以來看看,SyncManager類的定義了,其實很簡單。

class SyncManager(BaseManager):
    '''
    Subclass of `BaseManager` which supports a number of shared object types.

    The types registered are those intended for the synchronization
    of threads, plus `dict`, `list` and `Namespace`.

    The `multiprocessing.Manager()` function creates started instances of
    this class.
    '''

SyncManager.register('Queue', Queue.Queue)
SyncManager.register('JoinableQueue', Queue.Queue)
SyncManager.register('Event', threading.Event, EventProxy)
SyncManager.register('Lock', threading.Lock, AcquirerProxy)
SyncManager.register('RLock', threading.RLock, AcquirerProxy)
SyncManager.register('Semaphore', threading.Semaphore, AcquirerProxy)
SyncManager.register('BoundedSemaphore', threading.BoundedSemaphore,
                     AcquirerProxy)
SyncManager.register('Condition', threading.Condition, ConditionProxy)
SyncManager.register('Pool', Pool, PoolProxy)
SyncManager.register('list', list, ListProxy)
SyncManager.register('dict', dict, DictProxy)
SyncManager.register('Value', Value, ValueProxy)
SyncManager.register('Array', Array, ArrayProxy)
SyncManager.register('Namespace', Namespace, NamespaceProxy)

# types returned by methods of PoolProxy
SyncManager.register('Iterator', proxytype=IteratorProxy, create_method=False)
SyncManager.register('AsyncResult', create_method=False)

上面的Queue()、Event()等等都是該類的方法,比如Event(),它是創建一個共享的threading.Event對象並返回一個代理。當然除了上面這些外,其實我們也可以用register()向管理器註冊新的類型,如下:

#coding=utf-8
from multiprocessing.managers import BaseManager

class MathsClass(object):
    def add(self, x, y):
        return x + y
    def mul(self, x, y):
        return x * y

class MyManager(BaseManager):
    pass

MyManager.register('Maths', MathsClass)

if __name__ == '__main__':
    manager = MyManager()
    manager.start()
    maths = manager.Maths()
    print maths.add(4, 3)         # prints 7
    print maths.mul(7, 8)         # prints 56

下面看個簡單的例子

#coding=utf-8
import multiprocessing

def fun(ns):
    ns.x.append(1)
    ns.y.append('x')
   

if __name__ == '__main__':
    manager = multiprocessing.Manager()
    ns = manager.Namespace()
    ns.x = []
    ns.y = []
    print "before",ns
    p = multiprocessing.Process(target=fun,args=(ns))
    p.start()
    p.join()
    print "after",ns

本程序的目的是想得到x=[1],y=['x'],但是沒有得到,這是爲什麼呢?這是因爲manager對象僅能傳播一個可變對象本身所做的修改,如果一個manager.list()對象,管理列表本身的任何更改會傳播到所有其他進程,但是如果容器對象內部還包括可修改對象,則內部可修改對象的任何更改都不會傳播到其他進程。上面例子中,ns是一個容器,它本身的改變會傳播到所有進程,但是它的內部對象x,y是可變對象,它們的改變不會傳播到其他進程,所有沒有得到我們所要的結果。可以作如下修改:

#coding=utf-8
import multiprocessing

def fun(ns,x,y):
    x.append(1)
    y.append('x')
    ns.x = x
    ns.y = y


if __name__ == '__main__':
    manager = multiprocessing.Manager()
    ns = manager.Namespace()
    ns.x = []
    ns.y = []
    print "before",ns
    p = multiprocessing.Process(target=fun,args=(ns,ns.x,ns.y,))
    p.start()
    p.join()
    print "after",ns

這個例子比較簡單,以後碰到好的例子,再跟大家分享。另外Python官方手冊上有很多幫助大家理解這些概念的例子,有興趣的可以去看看,今天就寫到這兒了,不正之處歡迎批評指正!下篇博文介紹進程池和線程池。








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