在Python中创建自定义Futures对象

时间:2017-06-30 22:57:18

标签: python multithreading future

我有一组简单的对象,用于使用Actor模型管理后台进程。在这种情况下,我只关心一个演员。但是,参与者在接收消息之间保持持久状态是很重要的。

对象通过将消息附加到主线程中的队列来工作。然后主线程可以随意执行。每隔一段时间,它会检查结果队列中是否有新内容。当发生这种情况时,它知道演员已完成任务。

我想知道是否可以使用Futures对象以更干净的方式实现。我目前的实施如下:

import multiprocessing
import time
import collections


class Client(object):
    """
    Object used in the main thread to communicate with background actors
    """
    def __init__(client):
        client.manager = None
        client.start()

    def __del__(client):
        if client.manager and client.manager.is_alive():
            client.get(StopIteration)

    def start(client):
        client.task_queue = multiprocessing.JoinableQueue()
        client.result_queue = multiprocessing.Queue()
        client.result_history = collections.deque(maxlen=1000)
        client.manager = Manager(client.task_queue, client.result_queue)
        client.manager.start()

    def post(client, payload):
        client.task_queue.put(payload)

    def get(client, payload):
        # Exhaust any existing results
        list(client.results())
        # Post the command
        client.post(payload)
        # Wait for a response
        result = client.wait_for_result()
        return result

    def wait_for_result(client):
        wait = 0
        while True:
            for result in client.results():
                return result
            time.sleep(wait)
            wait = max(1, wait + .01)

    def results(client):
        """ Look at results put on the result_queue """
        while not client.result_queue.empty():
            item = client.result_queue.get()
            client.result_history.append(item)
            yield item


class Manager(multiprocessing.Process):
    """
    Manager manages a single actor.
    A manager sends messages an actor and appends a response when it is done.
    """
    def __init__(self, task_queue, result_queue):
        super(Manager, self).__init__()
        self.task_queue = task_queue
        self.result_queue = result_queue

    def run(self):
        """ main loop """
        terminate = False
        # Create Actor in separate process and send messages to it
        actor = Actor()

        while not terminate:
            message = self.task_queue.get()
            print('Sending message={} to actor'.format(message))
            try:
                if message is StopIteration:
                    content = 'shutdown'
                    terminate = True
                else:
                    content = actor.handle(message)
            except Exception as ex:
                print('Error handling message')
                status = 'error'
                content = repr(ex)
            else:
                status = 'success'
                print('Actor finished handling message={}'.format(message))

            # Send back result
            response = {
                'status': status,
                'content': content
            }
            self.task_queue.task_done()
            self.result_queue.put(response)
        print('Manager is shutting down')


class Actor(object):
    """
    An actor is given messages from its manager and performs actions in a
    single thread. Its state is private and threadsafe.
    """
    def __init__(actor):
        actor.state = {}

    def handle(actor, message):
        if not isinstance(message, dict):
            raise ValueError('Commands must be passed in a message dict')
        message = message.copy()
        action = message.pop('action', None)
        if action is None:
            raise ValueError('message must have an action item')
        if action == 'hello world':
            content = 'hello world'
            return content
        elif action == 'debug':
            return actor
        elif action == 'start':
            actor.state['a'] = 3
            return 'started'
        elif action == 'add':
            for i in range(10000000):
                actor.state['a'] += 1
            return 'added', actor.state['a']
        else:
            raise ValueError('Unknown action=%r' % (action,))


def test():
    print('Starting Test')
    client = Client()
    print('About to send messages')
    # Get sends a message and then blocks until the response is returned. 
    print(client.get({'action': 'hello world'}))
    print(client.get({'action': 'start'}))
    print(client.get({'action': 'add'}))
    print('Test completed')


if __name__ == '__main__':
    test()

我想修改此代码以使用Future对象。每当客户端要发送消息时,是否可以创建Future对象,然后通过多处理队列发送它?然后管理器可以执行actors函数,然后修改Future对象的状态,而不是将结果附加到result_queue。

这似乎可以提供一种更清晰的方式将结果与发送给actor的消息相关联。它还将消除对第一个示例中的get和results方法的需要。

直观地说,我希望它看起来像这样:

from concurrent import futures
import multiprocessing


class Client(object):
    """
    Object used in the main thread to communicate with background actors
    """
    def __init__(client):
        client.manager = None
        client.start()

    def __del__(client):
        if client.manager and client.manager.is_alive():
            f = client.post(StopIteration)

    def start(client):
        client.task_queue = multiprocessing.JoinableQueue()
        client.manager = Manager(client.task_queue)
        client.manager.start()

    def post(client, payload):
        f = futures.Future()
        client.task_queue.put((f, payload))
        return f


class Manager(multiprocessing.Process):
    """
    Manager manages a single actor.
    """
    def __init__(self, task_queue):
        super(Manager, self).__init__()
        self.task_queue = task_queue

    def run(self):
        """ main loop """
        terminate = False
        # Create Actor in separate process and send messages to it
        actor = Actor()

        while not terminate:
            f, message = self.task_queue.get()
            f.set_running_or_notify_cancel()
            print('Sending message={} to actor'.format(message))
            try:
                if message is StopIteration:
                    content = 'shutdown'
                    terminate = True
                else:
                    content = actor.handle(message)
            except Exception as ex:
                print('Error handling message')
                status = 'error'
                content = repr(ex)
            else:
                status = 'success'
                print('Actor finished handling message={}'.format(message))

            # Send back result
            response = {
                'status': status,
                'content': content
            }
            self.task_queue.task_done()
            f.set_result(response)
        print('Manager is shutting down')


class Actor(object):
    """
    An actor is given messages from its manager and performs actions in a
    single thread. Its state is private and threadsafe.
    """
    def __init__(actor):
        actor.state = {}

    def handle(actor, message):
        if not isinstance(message, dict):
            raise ValueError('Commands must be passed in a message dict')
        message = message.copy()
        action = message.pop('action', None)
        if action is None:
            raise ValueError('message must have an action item')
        if action == 'hello world':
            content = 'hello world'
            return content
        elif action == 'debug':
            return actor
        elif action == 'start':
            actor.state['a'] = 3
            return 'started'
        elif action == 'add':
            for i in range(10000000):
                actor.state['a'] += 1
            return 'added', actor.state['a']
        else:
            raise ValueError('Unknown action=%r' % (action,))


def test():
    print('Starting Test')
    client = Client()
    print('About to send messages')
    f1 = client.post({'action': 'hello world'})
    print(f1.result())
    f2 = client.post({'action': 'start'})
    print(f2.result())
    f3 = client.post({'action': 'add'})
    print(f3.result())
    print('Test completed')

if __name__ == '__main__':
    test()

但是,这显然无法正确执行。我相信我需要某种进程池管理器来为我创建未来(因为我正在调用记录的方法,说只有池管理器应该调用它们)。但我不太清楚如何去做。我之前使用过期来映射单例工作者函数,但我以前从未管理过具有状态的外部进程。

有人可以帮我解决这个问题吗?或许还有一种更简单的方法可以用Futures实现这个目标吗?

1 个答案:

答案 0 :(得分:0)

所以,我继续前进,只是创建了一个库来执行此操作:

https://github.com/Erotemic/futures_actors