簡體   English   中英

與SocketServer實例的Python多處理通信

[英]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__方法(正在處理交談)之間沒有共享存儲的信息。 (服務器類是可調用的,因為我還需要其他東西。我認為這與問題無關。)

我的問題是,這項工作是否會像我想象的那樣? multiprocessingthreadingsocketserver可以在同一台計算機上一起良好運行? 我對使用其他機制在進程之間進行通信(例如QueuePipe )不感興趣。 我有一個可行的解決方案。 我想知道這種方法是否可行,即使效率較低。 而且,如果是的話,我在做什么錯而導致其無法正常工作?

下面是一個說明問題的最小示例:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM