Python 多程式的自定義共享資料型別
最近專案要用到Python多程式,程式是1+N式的,1用來向一個資料結構中不斷的寫資料,其他N個程式從同時從這個資料結構中讀資料,每條資料都是一次性的。因此:
- 當前程式可以作為這裡的1程式,然後預設新建N個子程式。N的大小一般和CPU核數相同,如果是生產環境,不想讓程式佔滿伺服器的資源,可以再設定一個最大程式數。
- 因為是共享操作,這個資料結構就需要是程式安全的。
在網上查了一些關於Python多程式的資料,發現大多都只是從官方文件中隨便找了些程式碼(有的連改都沒改),都是些不能用來實用的初級知識,實在無法稱其為教程(這裡面就包括我之前寫的Python多程式文章)。經過我不斷的篩選和閱讀官方文件,終於讓我找到了一個可以用的方法。
這裡先列出我找到的對我有幫助的文章列表:
正式寫程式碼之前,先要說清楚幾個點。
理解多程式
這點可以參考下我之前寫的文章執行緒、程式、協程。對於一個程式來說,如果其呼叫了join()
,它會阻塞自己,一直要等到其他程式都退出後才會繼續執行下去。
這就解決了我的第一個問題,就是讓主程式作為1程式不斷寫入資料,具體的操作方法就是在新建子程式時不呼叫其join()
方法。
由淺入深的幾個例子
multiprocessing
庫提供了兩種方式建立子程式Process
類和Pool
類,前者需要對每個子程式進行操作,包括通訊、同步和共享;後者則是對前者的封裝,其維護了一定的程式池並實現了非同步操作。當然還有一些其他的操作,這裡就不贅述了。
場景
我這裡需要的共享資料結構是一個最小堆,堆中的儲存的是一條條記錄(大概30W條),並按照某個記錄中的時間戳排序。1程式會定時重新整理這個堆,N程式會不斷從堆中pop資料,處理後根據新的時間戳再push進堆中。
這其中其實涉及到了幾個點:建立多程式、給子程式傳入引數、自定義堆結構、共享鎖。這裡我一一解釋。
建立子程式並傳入引數的方式
前面說了,建立子程式一般來說有兩種方式:Process和Pool,這裡我用了Process。為什麼不用Pool呢?Pool的優勢是可以使用非同步操作,但這裡有個問題就是普通的multiprocess.Lock不支援Pool,需要使用Manager().Lock()才行,這樣就顯得笨重了,加上我這個專案對非同步要求並不高,就採用了Process類用來建立子程式。
這部分程式碼網上搜一下有很多,我也放出我的測試程式碼:
from multiprocessing import Process
import os
class TestClass(object):
def __init__(self, *args, **kwargs):
pass
def func(self, i):
print('SubProcess[{}]: {}'.format(i, os.getpid()))
def start(self):
print('Main Process: {}, begin'.format(os.getpid()))
processes = [Process(target=self.func, args=(i,)) for i in range(3)]
for p in processes:
p.start()
for p in processes:
p.join()
print('Main Process: {}, end'.format(os.getpid()))
if __name__ == '__main__':
TestClass().start()
這裡有個地方讓我疑惑了很久,因為官網上有一塊程式碼是這樣的:
from multiprocessing import Process, Lock
def f(l, i):
l.acquire()
print 'hello world', i
l.release()
if __name__ == '__main__':
lock = Lock()
for num in range(10):
Process(target=f, args=(lock, num)).start()
這一度讓我以為只要用Process+Lock就能滿足我的需求了。試了之後發現其實這樣是沒辦法在程式間共享資料的(每個程式都有獨立的PCB,還記得嗎)。這樣只能保證多程式在訪問公共資源時不會產生衝突(比如說標準輸入輸出,也就是上面這個例子)。如果要程式間共享資料,需要使用Queue
或Pipe
,或共享記憶體(Value
或Array
形式),或用Manager
(後面會說),不管哪種,都不能滿足我“自定義共享最小堆”的需求。
自定義共享資料
這節和多程式沒關係,主要是講我要用到的這個資料結構。關於堆是什麼我就不解釋了。Python中有個叫heapq
的庫,這個庫中,堆中的資料可以是一個tuple,在排序比較的時候會優先比較tuple[0]
的內容,如果相等再比較tuple[1]
……以此類推。文件中有一個關於優先順序序列的實現很有參考意義,這也要求tuple中的幾個資料必須是可以比較的(物件實現了__lt__
, __gt__
,__eq__
方法)。這裡我要實現的是一個支援自定義比較函式的堆,我稱為HeapQueueWithComparer
,直接給出程式碼:
"""支援自定義比較函式(cmp)的堆佇列heap queue"""
import heapq
class HeapQueueWithComparer(object):
def __init__(self, initial=None, comparer=lambda x: x, heapify=heapq.heapify):
self.comparer = comparer
self.data = []
if initial:
self.data = [(self.comparer(item), item) for item in initial]
heapify(self.data)
else:
self.data = []
def push(self, item, heappush=heapq.heappush):
heappush(self.data, (self.comparer(item), item))
def pop(self, heappop=heapq.heappop):
return heappop(self.data)[1] if self.data else None
其原理就是根據comparer
生成一個特徵值,然後將其和資料本身作為一個元組在堆中進行排序。用起來的時候可以這麼用:
import random
from collections import namedtuple
volume_t = namedtuple('volume_t', ['length', 'width', 'height', 'id'])
def __get_rand():
return random.randint(1, 10)
data = [volume_t(__get_rand(), __get_rand(), __get_rand(), i) for i in range(5)]
heap_cmp = HeapQueueWithComparer(data, lambda x: x.length*x.width*x.height)
[print(heap_cmp.pop()) for i in range(len(data))]
這裡是根據一個長方體的體積排序,用namedtuple
的原因是使程式碼更可讀,理論上用tuple
是一樣的。
鎖
接下來要解決的一個問題就是如何讓這個堆再多個程式中共享並且不會產生不一致的問題。前文其實提了,程式間共享資料的一種方式是使用server process,實際中使用就是multiprocessing.managers
,它會新建一個額外的程式用來管理共享的資料,而其本身會作為一個代理,每次子程式需要操作共享資料,實際上都是通過這個server process進行操作的,文件中稱之為proxy。一個manager
物件支援list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Queue, Value and Array。因此,我們需要自定義一個Manager,並讓它支援帶鎖的操作。這裡提一句,Manager也可以是遠端的,不過這裡用不到。
自定義Manager需要繼承BaseManager,測試程式碼如下。注意這裡和上一個例子中的程式碼並不完全一致,我是覺得再用lambda對程式碼的可讀性影響太大了。:
import heapq
import os
import random
import time
from collections import namedtuple
from multiprocessing import Lock, Process
from multiprocessing.managers import BaseManager
class HeapManager(BaseManager):
def __init__(self):
super().__init__()
self.lock = Lock()
self.data = []
def sync(self, rules_changed):
with self.lock:
if self.data:
self.data[:] = []
self.data = [(item[0]*item[1]*item[2], item,) for item in rules_changed]
heapq.heapify(self.data)
def pop(self):
with self.lock:
return heapq.heappop(self.data)[1] if self.data else None
def push(self, item):
with self.lock:
heapq.heappush(self.data, (item[0]*item[1]*item[2], item,))
HeapManager.register('HeapManager', HeapManager)
volume_t = namedtuple('volume_t', ['length', 'width', 'height', 'id'])
class TestClass(object):
def __init__(self, *args, **kwargs):
manager = HeapManager()
manager.start()
self.hm = manager.HeapManager()
self.lock = Lock()
def func(self, i):
while True:
with self.lock:
item = self.hm.pop()
if not item:
print('Begin to sleep 2s.')
time.sleep(2)
else:
print('Process-{} {} {}'.format(i, os.getpid(), item))
def start(self):
jobs = [Process(target=self.func, args=(i, )) for i in range(3)]
[j.start() for j in jobs]
# [j.join() for j in jobs] # 不用這行可以不阻塞主程式
def __get_rand():
return random.randint(1, 10)
while True:
data = [volume_t(__get_rand(), __get_rand(), __get_rand(), i) for i in range(10)]
with self.lock:
self.hm.sync(data)
print('Main {}, add {}.'.format(os.getpid(), data))
print(sorted(data))
time.sleep(5)
if __name__ == '__main__':
TestClass().start()
相關文章
- 自定義資料型別資料型別
- DM自定義資料型別資料型別
- SQL Server 中自定義資料型別SQLServer資料型別
- MapReduce程式設計實踐之自定義資料型別程式設計資料型別
- 自主資料型別:在TVM中啟用自定義資料型別探索資料型別
- 自定義型別型別
- 多型關聯自定義的型別欄位的處理多型型別
- Hadoop-MapReduce之自定義資料型別Hadoop資料型別
- 定義多維的點模板類,任意資料型別資料型別
- Kettle自定義資料庫連線型別連線HGDB資料庫型別
- Hibernate使用者自定義資料型別問題資料型別
- 型別自定義格式字串型別字串
- Pl/SQL 自定義型別SQL型別
- ORACLE 自定義型別[轉]Oracle型別
- Python程式設計常用的資料型別Python程式設計資料型別
- UnrealEngine建立自定義資產型別Unreal型別
- Linq to sql 自定義型別SQL型別
- ros|自定義訊息型別ROS型別
- C# 泛型集合的自定義型別排序C#泛型型別排序
- python 自定義資料分頁Python
- EF:自定義Oracle的對映型別Oracle型別
- Python資料型別Python資料型別
- 資料型別是什麼?Python的資料型別又有哪些?資料型別Python
- Python常用的資料型別Python資料型別
- Python的基本資料型別Python資料型別
- python的資料型別(集合)Python資料型別
- 建立自定義塊 - 型別檢查型別
- python 魔術方法 : 讓自定義類更像內建型別Python型別
- Python之資料型別Python資料型別
- python自學——資料型別Python資料型別
- python基本資料型別Python資料型別
- Python資料型別3Python資料型別
- Python3的資料型別Python資料型別
- Python的資料型別總結Python資料型別
- python的基礎資料型別Python資料型別
- JumpList中Recent類別和自定義型別薦型別
- Android 自定義構建型別 BuildTypeAndroid型別UI
- MyBatis使用自定義TypeHandler轉換型別MyBatis型別