Search code examples
pythonpython-3.xmultiprocessingpython-multiprocessing

How can I update class members in processes?


I have looked for other questions, and this un-accepted-answered question is the only one I could find that somehow covers this issue and is not really helpful. Also, I need this to work with processes, and not threads.

So from the ground up I wrote a sample program to show my issue, you should be able to paste it and it will run:

import multiprocessing
import time 

class Apple:
   def __init__(self, color):
      self.color = color

def thinkAboutApple(apple):
   while True:
      print(apple.color)
      time.sleep(2)

my_apple = Apple("red")
new_process = multiprocessing.Process(target=thinkAboutApple, args=(my_apple,))
new_process.start()
time.sleep(4)
print("new: brown")
my_apple.color = "brown"

#so that the program doesn't exit after time.sleep(4)
while True:
    pass
# actual output | # wanted output
red             | red
red             | red
new: brown      | new: brown
red             | brown
red             | brown

This tells me that either the apple is in a weird supposition where it is two colours at the same time, OR that the new_process' apple is in another position in ram and separated from the apple in the main process.

So the question is: Is there a way to have the pointer of the apple in the process point to the same apple, or what is the pythonic way to keep all instances of the apple in all processes the same? What if I have the same apple in many processes and even more processes without the apple, how do I make sure they area always the same?


Solution

  • You can derive a specialized version of a Proxy class used by multiprocessing.BaseManager from the (undocumented) multiprocessing.managers.NamespaceProxy class that, unlike the base class, exposes all of its methods and attributes. This is similar to @shtse8's answer to the linked duplicate question, but I'm posting a runnable answer to it here to make clear how it can be done.

    from multiprocessing import Process
    from multiprocessing.managers import BaseManager, NamespaceProxy
    import time
    import types
    
    class MyManager(BaseManager): pass  # Avoid namespace pollution.
    
    class Apple:
        def __init__(self, color):
            self.color = color
    
    
    def Proxy(target):
        """ Create a derived NamespaceProxy class for `target`. """
        def __getattr__(self, key):
            result = self._callmethod('__getattribute__', (key,))
            if isinstance(result, types.MethodType):
                def wrapper(*args, **kwargs):
                    self._callmethod(key, args)
                return wrapper
            return result
    
        dic = {'types': types, '__getattr__': __getattr__}
        proxy_name = target.__name__ + "Proxy"
        ProxyType = type(proxy_name, (NamespaceProxy,), dic)  # Create subclass.
        ProxyType._exposed_ = tuple(dir(target))
    
        return ProxyType
    
    
    AppleProxy = Proxy(Apple)
    
    
    def thinkAboutApple(apple):
        while True:
            print(f"apple.color: {apple.color}")
            time.sleep(1)
    
    
    if __name__ == '__main__':
    
        MyManager.register('Apple', Apple, AppleProxy)
    
        manager = MyManager()
        manager.start()
    
        my_apple = manager.Apple("red")
        new_process = Process(target=thinkAboutApple, args=(my_apple,))
        new_process.start()
    
        time.sleep(2)  # Allow other process to run a short while.
        my_apple.color = "brown"  # Change shared class instance.
    
        time.sleep(2)  # Allow other process to run at little while longer.
        new_process.terminate()