繁体   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