Search code examples
pythonmultithreadingmultiprocessingblocking

Python multiprocessing.managers.BaseManager running registered callable function sequentially


I am working with a remote manager provided by Python's multiprocessing library. I have setup a remote server using BaseManager, to which multiple clients connect simultaneously. Unfortunately, my server is serving requests sequentially for each client. My server is supposed to make a network call to Google's directions API to return distance and time.

My understanding was that a new thread would be spawned for each client that connects, so i would not face this problem.

I have provided a sample of my code in a simplified manner.

Here is the server code:

import time
from multiprocessing.managers import BaseManager
import threading

class DistanceTime:

    def get_distance_time(self):
        print('started by thread %s'%(threading.get_ident()))
        # assume that network request was made here
        time.sleep(2)
        print('ended by thread %s'%(threading.get_ident()))

def server():
    distance_time=DistanceTime()
    BaseManager.register('get_distance_time', callable=distance_time.get_distance_time)
    manager = BaseManager(address=('localhost', 5000), authkey=b'abracadabra')
    server = manager.get_server()
    print('server running')
    server.serve_forever()

server()

Here is the client code:

from multiprocessing.managers import BaseManager
from concurrent.futures import ThreadPoolExecutor
import time

def client():
    BaseManager.register('get_distance_time')
    manager = BaseManager(address=('localhost', 5000), authkey=b'abracadabra')
    manager.connect()
    executor = ThreadPoolExecutor(max_workers=3)
    # client mades three simultaneous requests to the server
    b=executor.submit(manager.get_distance_time)
    b=executor.submit(manager.get_distance_time)
    c=executor.submit(manager.get_distance_time)
    print('done')
    time.sleep(5)

client()

Even though the client sends all three requests immediately, the server prints the following:

server running
started by thread 16740
ended by thread 16740
started by thread 4712
ended by thread 4712
started by thread 7132
ended by thread 7132

Ideally, all started prints should come together. This is a major bottleneck for my application.


Solution

  • the callable you're registering is a 'creation' method and these are always run in a locked context, but the object it returns is automatically proxied and any methods invoked on it aren't automatically locked

    in your demo code, I'd change:

    def server():
        distance_time=DistanceTime()
        BaseManager.register('get_distance_time', callable=distance_time.get_distance_time)
    

    to be:

    def server():
        distance_time = DistanceTime()
        BaseManager.register('DistanceTime', lambda: distance_time)
    

    and then use this as:

    distance_time = manager.DistanceTime()
    a = executor.submit(distance_time.get_distance_time)
    b = executor.submit(distance_time.get_distance_time)
    c = executor.submit(distance_time.get_distance_time)
    

    which should allow everything go in parallel. I've not actually tested this, but will if you say this doesn't work...

    not that it matters here, but I generally feel it's better to register these sorts of things in a separate/derived Manager