Multiprocessing event-queue not updating

Question:

So I’m writing a program with an event system.
I got a list of events to be handled.
One Process is supposed to push to the handler-list new events.
This part seems to work as I tried to print out the to-handle-list after pushing one event.
It gets longer and longer, while, when I print out the to handle list in the handle-event method, it is empty all the time.

Here is my event_handler code:

class Event_Handler:
    def __init__(self):
        self._to_handle_list = [deque() for _ in range(Event_Prio.get_num_prios()) ]            
        self._controll_handler= None
        self._process_lock = Lock() 
       
    def init(self, controll_EV_handler):
        self._controll_handler= controll_EV_handler



    def new_event(self, event):          #adds a new event to list
        with self._process_lock:
            self._to_handle_list[event.get_Prio()].append(event) #this List grows 
 
 
    def handle_event(self):         #deals with the to_handle_list
        self._process_lock.acquire()
        
        for i in range(Event_Prio.get_num_prios()):  #here i keep a list of empty deque
            print(self._to_handle_list)
            if (self._to_handle_list[i]): #checks if to-do is empty, never gets here that its not
                self._process_lock.release()
                self._controll_handler.controll_event(self._to_handle_list[i].popleft())
                return
        self._process_lock.release()
 

    def create_Event(self, prio, type):
        return Event(prio, type)

I tried everything. I checked if the event-handler-id is the same for both processes (plus the lock works)
I even checked if the to-handle-list-id is the same for both methods; yes it is.
Still the one in the one process grows, while the other is empty.
Can someone please tell me why the one list is empty?

Edit: It works just fine if I throw a event through the system with only one process. has to do sth with multiprocessing

Edit: Because someone asked, here is a simple usecase for it(I only used the essentials):

class EV_Main():
    def __init__(self):
        self.e_h = Event_Handler()
        self.e_controll = None  #the controller doesnt even matter because the controll-function never gets called....list is always empty 

    
    def run(self):
        
        self.e_h.init(self.e_controll)
        process1 = Process(target = self.create_events)
        process2 = Process(target = self.handle_events)
        process1.start()
        process2.start()
    def create_events(self):
        while True:
            self.e_h.new_event(self.e_h.create_Event(0, 3))    # eEvent_Type.S_TOUCH_EVENT
            time.sleep(0.3)
    def handle_events(self):
        while True:
            self.e_h.handle_event()
            time.sleep(0.1)

Asked By: Marsh

||

Answers:

To have a shareable set of deque instances, you could create a special class DequeArray which will hold an internal list of deque instances and expose whatever methods you might need. Then I would turn this into a shareable, managed object. When the manager creates an instance of this class, what is returned is a proxy to the actual instance that resides in the manager’s address space. Any method calls you make on this proxy are actually shipped of to the manager’s process using pickle and any results returned the same way. Since the individual deque instances are not shareable, managed objects, do not add a method that returns one of these deque instances which is then modified without being cognizant that the version of the deque in the manager’s address space has not been modified.

Individual operations on a deque are serialized. But if you are doing some operation on a deque that consists of multiple method calls on the deque and you require atomicity, then that sequence is a critical section that needs to be done under control of a lock, as in the left_rotate function below.

from multiprocessing import Process, Lock
from multiprocessing.managers import BaseManager
from collections import deque

# Add methods to this as required:
class DequeArray:
    def __init__(self, array_size):
        self._deques = [deque() for _ in range(array_size)]

    def __repr__(self):
        l = []
        l.append('DequeArray [')
        for d in self._deques:
            l.append('    ' + str(d))
        l.append(']')
        return 'n'.join(l)

    def __len__(self):
        """
        Return our length (i.e. the number of deque
        instances we have).
        """
        return len(self._deques)

    def append(self, i, value):
        """
        Append value to the ith deque
        """
        self._deques[i].append(value)

    def popleft(self, i):
        """
        Eexcute a popleft operation on the ith deque
        and return the result.
        """
        return self._deques[i].popleft()

    def length(self, i):
        """
        Return length of the ith dequeue.
        """
        return len(self._deques[i])


class DequeArrayManager(BaseManager):
    pass

DequeArrayManager.register('DequeArray', DequeArray)

# Demonstrate how to use a sharable DequeArray

def left_rotate(deque_array, lock, i):
    # Rotate first element to be last element:
    # This is not an atomic operation, so do under control of a lock:
    with lock:
        deque_array.append(i, deque_array.popleft(i))


# Required for Windows:
if __name__ == '__main__':
    # This starts the manager process:
    with DequeArrayManager() as manager:
        # Two deques:
        deque_array = manager.DequeArray(2)
        # Initialize with some values:
        deque_array.append(0, 0)
        deque_array.append(0, 1)
        deque_array.append(0, 2)

        # Same values in second deque:
        deque_array.append(1, 0)
        deque_array.append(1, 1)
        deque_array.append(1, 2)
        print(deque_array)

        # Both processses will be modifying the same deque in a
        # non-atomic way, so we definitely need to be doing this under
        # control of a lock. We don't care which process acquires the
        # lock first because the results will be the same regardless.
        lock = Lock()
        p1 = Process(target=left_rotate, args=(deque_array, lock, 0))
        p2 = Process(target=left_rotate, args=(deque_array, lock, 0))
        p1.start()
        p2.start()
        p1.join()
        p2.join()

        print(deque_array)

Prints:

DequeArray [
    deque([0, 1, 2])
    deque([0, 1, 2])
]
DequeArray [
    deque([2, 0, 1])
    deque([0, 1, 2])
]
Answered By: Booboo
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.