Home >Backend Development >Python Tutorial >Concurrency Patterns: Active Object
The Active Object Pattern is a concurrency design pattern that decouples method execution from method invocation. The primary goal of this pattern is to introduce asynchronous behavior by executing operations in a separate thread, while providing a synchronous interface to the client. This is achieved using a combination of message passing, request queues, and scheduling mechanisms.
Let's say we need to do a computation, maybe a API call, a database query, etc. I am not going to implement any exception handling because I am too lazy.
def compute(x, y): time.sleep(2) # Some time taking task return x + y
Below is an example of how we might handle concurrent requests without using the Active Object Pattern.
import threading import time def main(): # Start threads directly results = {} def worker(task_id, x, y): results[task_id] = compute(x, y) print("Submitting tasks...") thread1 = threading.Thread(target=worker, args=(1, 5, 10)) thread2 = threading.Thread(target=worker, args=(2, 15, 20)) thread1.start() thread2.start() print("Doing other work...") thread1.join() thread2.join() # Retrieve results print("Result 1:", results[1]) print("Result 2:", results[2]) if __name__ == "__main__": main()
Thread Management: Direct management of threads increases complexity, especially as the number of tasks grows.
Lack of Abstraction: The client is responsible for managing the lifecycle of threads, coupling task management with business logic.
Scalability Issues: Without a proper queue or scheduling mechanism, there’s no control over task execution order.
Limited Responsiveness: The client has to wait for threads to join before accessing results.
Below is a Python implementation of the Active Object Pattern using threading and queues for doing the same thing as above. We'll walk through each part one by one:
MethodRequest: Encapsulates the method, arguments, and a Future to store the result.
def compute(x, y): time.sleep(2) # Some time taking task return x + y
Scheduler: Continuously processes requests from the activation_queue in a separate thread.
import threading import time def main(): # Start threads directly results = {} def worker(task_id, x, y): results[task_id] = compute(x, y) print("Submitting tasks...") thread1 = threading.Thread(target=worker, args=(1, 5, 10)) thread2 = threading.Thread(target=worker, args=(2, 15, 20)) thread1.start() thread2.start() print("Doing other work...") thread1.join() thread2.join() # Retrieve results print("Result 1:", results[1]) print("Result 2:", results[2]) if __name__ == "__main__": main()
Servant: Implements the actual logic (e.g., the compute method).
class MethodRequest: def __init__(self, method, args, kwargs, future): self.method = method self.args = args self.kwargs = kwargs self.future = future def execute(self): try: result = self.method(*self.args, **self.kwargs) self.future.set_result(result) except Exception as e: self.future.set_exception(e)
Proxy: Translates method calls into requests and returns a Future for the result.
import threading import queue class Scheduler(threading.Thread): def __init__(self): super().__init__() self.activation_queue = queue.Queue() self._stop_event = threading.Event() def enqueue(self, request): self.activation_queue.put(request) def run(self): while not self._stop_event.is_set(): try: request = self.activation_queue.get(timeout=0.1) request.execute() except queue.Empty: continue def stop(self): self._stop_event.set() self.join()
Client: Submits tasks asynchronously and retrieves results when needed.
import time class Servant: def compute(self, x, y): time.sleep(2) return x + y
The Active Object Pattern is a powerful tool for managing asynchronous operations in multi-threaded environments. By separating method invocation from execution, it ensures better responsiveness, scalability, and a cleaner codebase. While it comes with some complexity and potential performance overhead, its benefits make it an excellent choice for scenarios requiring high concurrency and predictable execution. However, its use depends on the specific problem at hand. As with most patterns and algorithms, there is no one-size-fits-all solution.
Wikipedia - Active Object
The above is the detailed content of Concurrency Patterns: Active Object. For more information, please follow other related articles on the PHP Chinese website!