[英]Python multiprocessing communication with SocketServer instances
我有一組過程,我們稱它們為A,B和C,它們需要相互通信。 A需要與B和C通信; B需要與A和C通信; C和C需要與A和B進行通信。A,B和C可以位於不同的計算機上或同一計算機上。
我的想法是通過套接字進行通信,如果它們都在同一台機器上(例如,端口11111處的A,端口22222處的B,等等),則使用“ localhost”。 這樣,非本地進程將被視為本地進程。 為此,我想我將為A,B和C分別設置一個SocketServer實例,並且每個實例都將知道其他兩個的地址。 每當需要進行通信時(例如,從A到B),A都會打開B的套接字並寫入數據。 然后,B不斷運行的服務器將讀取數據並將其存儲在列表中,以便以后在需要時使用。
我finish_request
的問題是, finish_request
方法(正在處理偵聽)和__call__
方法(正在處理交談)之間沒有共享存儲的信息。 (服務器類是可調用的,因為我還需要其他東西。我認為這與問題無關。)
我的問題是,這項工作是否會像我想象的那樣? multiprocessing
, threading
和socketserver
可以在同一台計算機上一起良好運行? 我對使用其他機制在進程之間進行通信(例如Queue
或Pipe
)不感興趣。 我有一個可行的解決方案。 我想知道這種方法是否可行,即使效率較低。 而且,如果是的話,我在做什么錯而導致其無法正常工作?
下面是一個說明問題的最小示例:
import uuid
import sys
import socket
import time
import threading
import collections
import SocketServer
import multiprocessing
class NetworkMigrator(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def __init__(self, server_address, client_addresses, max_migrants=1):
SocketServer.TCPServer.__init__(self, server_address, None)
self.client_addresses = client_addresses
self.migrants = collections.deque(maxlen=max_migrants)
self.allow_reuse_address = True
t = threading.Thread(target=self.serve_forever)
t.daemon = True
t.start()
def finish_request(self, request, client_address):
try:
rbufsize = -1
wbufsize = 0
rfile = request.makefile('rb', rbufsize)
wfile = request.makefile('wb', wbufsize)
data = rfile.readline().strip()
self.migrants.append(data)
print("finish_request:: From: %d To: %d MID: %d Size: %d -- %s" % (client_address[1],
self.server_address[1],
id(self.migrants),
len(self.migrants),
data))
if not wfile.closed:
wfile.flush()
wfile.close()
rfile.close()
finally:
sys.exc_traceback = None
def __call__(self, random, population, args):
client_address = random.choice(self.client_addresses)
migrant_index = random.randint(0, len(population) - 1)
data = population[migrant_index]
data = uuid.uuid4().hex
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect(client_address)
sock.send(data + '\n')
finally:
sock.close()
print(" __call__:: From: %d To: %d MID: %d Size: %d -- %s" % (self.server_address[1],
client_address[1],
id(self.migrants),
len(self.migrants),
data))
if len(self.migrants) > 0:
migrant = self.migrants.popleft()
population[migrant_index] = migrant
return population
def run_it(migrator, rand, pop):
for i in range(10):
pop = migrator(r, pop, {})
print(" run_it:: Port: %d MID: %d Size: %d" % (migrator.server_address[1],
id(migrator.migrants),
len(migrator.migrants)))
time.sleep(1)
if __name__ == '__main__':
import random
r = random.Random()
a = ('localhost', 11111)
b = ('localhost', 22222)
c = ('localhost', 33333)
am = NetworkMigrator(a, [b, c], max_migrants=11)
bm = NetworkMigrator(b, [a, c], max_migrants=22)
cm = NetworkMigrator(c, [a, b], max_migrants=33)
fun = [am, bm, cm]
pop = [["larry", "moe", "curly"], ["red", "green", "blue"], ["small", "medium", "large"]]
jobs = []
for f, p in zip(fun, pop):
pro = multiprocessing.Process(target=run_it, args=(f, r, p))
jobs.append(pro)
pro.start()
for j in jobs:
j.join()
am.shutdown()
bm.shutdown()
cm.shutdown()
查看此示例的輸出,將有三種打印類型:
run_it:: Port: 11111 MID: 3071227860 Size: 0
__call__:: From: 11111 To: 22222 MID: 3071227860 Size: 0 -- e00e0891e0714f99b86e9ad743731a00
finish_request:: From: 60782 To: 22222 MID: 3071227972 Size: 10 -- e00e0891e0714f99b86e9ad743731a00
如果migrants
在這種情況下出國,則“ MID”是該id
。 “ From”和“ To”是發送/接收傳輸的端口。 現在,我只是將數據設置為隨機的十六進制字符串,以便可以跟蹤各個傳輸。
我不明白為什么即使使用相同的MID,在某一時刻也會說它的大小為非零,然后在以后說它的大小是0。我覺得它必須源於以下事實:調用是多線程的。 如果使用這些行而不是最后兩個for
循環,則系統將按照我期望的方式工作:
for _ in range(10):
for f, p in zip(fun, pop):
f(r, p, {})
time.sleep(1)
那么破壞它的多處理版本會發生什么呢?
當我們創建3個新的NetworkMigrator對象時,將啟動3個新線程,每個線程都在監聽新的TCP連接。 稍后,我們為run_it函數啟動3個新進程。 總共我們有4個進程,第一個進程包含4個線程(1個主線程+ 3個服務器)。 現在,問題在於其他3個進程將無法訪問偵聽服務器線程對對象所做的更改。 這是因為默認情況下進程不共享內存。
因此,如果啟動3個新線程而不是進程,您將注意到不同之處:
pro = threading.Thread(target=run_it,args=(f,r,p))
還有另一個小問題。 線程之間的共享也不是完全安全的。 每當我們更改對象的狀態時,最好使用鎖。 最好在finish_request和call方法中執行以下操作。
lock = Lock()
...
lock.acquire()
self.migrants.append(data)
lock.release()
如果您對多線程不滿意,並且確實希望進行多處理,則可以按照以下說明使用代理對象: http : //docs.python.org/library/multiprocessing.html#proxy-objects
至於對象ID相同,這並不意外。 在該時間點,新過程將傳遞到對象的狀態(包括對象ID)。 新的過程繼續保留那些對象ID,但是我們在這里談論的是兩個完全不同的內存空間,因為它們是不同的過程。 因此,主流程所做的任何更改都不會反映在創建的子流程中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.