kquick / Thespian

Python Actor concurrency library
MIT License
189 stars 24 forks source link

Performance in python 3.9 and difference between multiprocTCPBase and multiporcQueueBase. #81

Closed FillyHolly closed 2 years ago

FillyHolly commented 2 years ago

Hello! I'm interested in several questions:

1) Is this project currently supported?

2) Does this project work on python 3.9?

3) I have code. And I don't understand multiprocTCPBase and multiprocQueueBase work. When using multiprocTCPBase the work does not terminate when ActorExitRequest occurs. But when use multiprocQueueBase, the whole process ends and the actor stop working. Why is that?

3.1) Option using multiprocTCPBase

from thespian.actors import *
from time import sleep

class Connect(object):
    def __init__(self, ipAddress, socketNum):
        self.ipAddress = ipAddress
        self.socketNum = socketNum

class MeasureValue(object):
    def __init__(self, measureCmd):
        self.measureCmd = measureCmd
    def __str__(self): return self.measureCmd

class CheckCMD(object):
    def __init__(self, disableCmd):
        self.disableCmd = disableCmd
    def __str__(self): return self.disableCmd

class Connection(ActorTypeDispatcher):
    def receiveMsg_Connect(self, message, sender):
        self.ipAddress = message.ipAddress
        self.socketNum = message.socketNum
        print('Connect to ', self.ipAddress, self.socketNum)

    def receiveMsg_MeasureValue(self, message, sender):
        answer = 'Voltage, Current, Power'
        self.meas = message
        print(self._sendSetCommand(self.meas, answer))
        sleep(1)
        self.send(self.myAddress, self.meas)

    def _sendSetCommand(self, cmd, answer):
        return {'Voltage': 0, 'Current': 0, 'Power': 0}

    def receiveMsg_CheckCMD(self, message, sender):
        print(message)

def main():
    asys = ActorSystem('multiprocTCPBase')
    try:
        conn = asys.createActor(Connection)
        asys.tell(conn, Connect('111.11.11.111', 1001))
        sleep(1)
        asys.tell(conn, MeasureValue(':MEASure1?'))
        sleep(3)
        asys.tell(conn, CheckCMD('someCMD'))
        sleep(3)
        asys.tell(conn, ActorExitRequest())
    finally:
        asys.shutdown()

if __name__ == '__main__':
    main()

3.2) Option using multiprocQueueBase

from thespian.actors import *
from time import sleep

class Connect(object):
    def __init__(self, ipAddress, socketNum):
        self.ipAddress = ipAddress
        self.socketNum = socketNum

class MeasureValue(object):
    def __init__(self, measureCmd):
        self.measureCmd = measureCmd
    def __str__(self): return self.measureCmd

class CheckCMD(object):
    def __init__(self, disableCmd):
        self.disableCmd = disableCmd
    def __str__(self): return self.disableCmd

class Connection(ActorTypeDispatcher):
    def receiveMsg_Connect(self, message, sender):
        self.ipAddress = message.ipAddress
        self.socketNum = message.socketNum
        print('Connect to ', self.ipAddress, self.socketNum)

    def receiveMsg_MeasureValue(self, message, sender):
        answer = 'Voltage, Current, Power'
        self.meas = message
        print(self._sendSetCommand(self.meas, answer))
        sleep(1)
        self.send(self.myAddress, self.meas)

    def _sendSetCommand(self, cmd, answer):
        return {'Voltage': 0, 'Current': 0, 'Power': 0}

    def receiveMsg_CheckCMD(self, message, sender):
        print(message)

def main():
    asys = ActorSystem('multiprocQueueBase')
    try:
        conn = asys.createActor(Connection)
        asys.tell(conn, Connect('111.11.11.111', 1001))
        sleep(1)
        asys.tell(conn, MeasureValue(':MEASure1?'))
        sleep(3)
        asys.tell(conn, CheckCMD('someCMD'))
        sleep(3)
        asys.tell(conn, ActorExitRequest())
    finally:
        asys.shutdown()

if __name__ == '__main__':
    main()
kquick commented 2 years ago
  1. This is an open source project, so there is no official "support" provided. I am happy to answer questions and provide assistance where I can, but no guarantees or warrantees are provided.
  2. It does. Do you have information to the contrary?
  3. I did not do a detailed comparison, so I'm assuming your two code bases are identical with the exception of the argument to ActorSystem in the main function. Looking over both, I see that receiveMsg_MeasureValue in the Connection actor will sleep (Actors are not recommended to sleep) and then sends itself the original message it received. This is essentially an infinite loop. More below:

Actors are not recommended to sleep, because this prevents them from handling any other messages. In the case of a multiprocTCPBase, the socket activity to send and receive messages is handled via non-blocking asynchronous code that runs when not running the receiveMessage code. Since the sleep causes the the Actor to block in the receiveMessage for most of the execution, the Actor does not have much opportunity to do the background processing.

For the multiprocTCPBase, this is probably filling the outbound and inbound TCP socket attempts, but the sleep is preventing them from being processed asynchronously and completing the receive of the CheckCMD and the ActorExitRequest. A transmit to self.myAddress bypasses any actual socket sending and is just placed back on the receive queue, which in this case immediately re-enters the receiveMessage handling, further preventing processing of the socket activity.

For the multiprocQueueBase, the send isn't asynchronous as it is for the multprocTCPBase, but the self-send bypasses the queueing and re-enters the receiveMessage, so it's also unlikely it is handling the CheckCMD and ActorExitRequest messages (although I cannot quite tell what exactly is happening from your description).

The general guide is that an Actor should not perform blocking operations in its receiveMessage method because that will prevent it from performing normal Actor-related activities. If you would like to schedule future activity in an Actor, I would recommend using the self.wakeupAfter() method to schedule that future activity and then directly returning from the receiveMessage method.

kquick commented 2 years ago

Closing this because there was no followup so I'm assuming I answered your questions satisfactorily. Please re-open or post another issue if you still have questions or problems.