【问题标题】:Creating custom Futures objects in Python在 Python 中创建自定义 Futures 对象
【发布时间】:2017-12-04 23:08:21
【问题描述】:

我有一组简单的对象,用于使用 Actor 模型管理后台进程。在这种情况下,我只关心一个演员。然而,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 对象,然后通过多处理队列发送它?然后管理器可以执行参与者函数,然后修改 Future 对象的状态,而不是将结果附加到 result_queue。

这似乎提供了一种更简洁的方式来将结果与发送给参与者的消息相关联。它还将消除对我在第一个示例中的 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 实现这一点?

【问题讨论】:

    标签: python multithreading future


    【解决方案1】:

    所以,我继续做一个图书馆来做这件事:

    https://github.com/Erotemic/futures_actors

    【讨论】:

      猜你喜欢
      • 2016-12-15
      • 2020-06-28
      • 1970-01-01
      • 1970-01-01
      • 2018-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-18
      相关资源
      最近更新 更多