Python

Python デザインパターンを学ぶ Poolパターン

概要

Poolパターンは、独立した状態の再利用可能なオブジェクトを作成して維持する。

プール自体はイテレート可能であり、ユーザーのケースに合わせて任意の方法で実装できる。重要なのは、オブジェクトのインスタンス化の数を最小限にすること。このパターンは以下のような場合に有効。

  • 新しいオブジェクトの構築にコストがかかる(時間的、空間的、帯域幅的な複雑さ)
  • オブジェクトを頻繁に出し入れする必要があるが、用途ごとに固有のオブジェクトを用意する必要がない
  • 常に少量のインスタンスしか使用していない

シンプルなワーカープールを実装例に示す。ワーカーをアクティブ/非アクティブにしたり、プールのサイズを管理するためのインターフェイスを実装する。

実装例

WorkerPool クラスが、ワーカーの作成と削除を制御する。ケースに応じて、シングルトンとして実装すると便利。オブジェクトの作成に時間がかかるような複雑なケースでは、プール内のワーカーを初期化するロジックをWorkerPoolのコンストラクタから分離すると良い可能性がある。

ワーカーが有効化されると、プールはアクティブなワーカーのリストを維持しつつ、_workersリストから削除する。ワーカーが無効化されると、_workersリストに戻される。Worker オブジェクトが内部状態を保持している場合、リセットする(またはデフォルトの休止状態に設定する)のに良いタイミング。

実行中にワーカープールのサイズを変更する必要がある場合、理想的なソリューションは、直後に再作成する必要のあるワーカーを削除せずにサイズ変更を実行すること。実装例のresizeメソッドでは、サイズ変更される数が制限内であることと、このプロセスがアクティブなワーカーに影響を与えないことを保証しながら、この問題に対処する方法の1つを示す。

class WorkerPoolError(Exception):
    pass


class Worker:
    pass


class WorkerPool:

    limit = 4

    def __init__(self, count):
        self.within_limit_check(count)
        self._workers = [Worker() for n in range(count)]
        self.active_count = 0

    def within_limit_check(self, count):
        if count > WorkerPool.limit or count <= 0:
            raise WorkerPoolError("Valid Worker Size")
        return True

    def activate_worker(self):
        self.active_count += 1
        return self._workers.pop()

    def deactivate_worker(self, worker):
        self.active_count -= 1
        self._workers.append(worker)

    def resize(self, new_count):

        self.within_limit_check(new_count)
        total_workers = len(self._workers) + self.active_count

        diff = new_count - total_workers

        if diff == 0:
            pass
        elif diff > 0:
            for n in range(diff):
                self._workers.append(Worker())
        elif diff < 0:
            if diff > len(self._workers):
                raise WorkerPoolError("Valid Worker Size")
            else:
                for n in range(abs(diff)):
                    self._workers.pop()


if __name__ == "__main__":

    print("Creating Worker pool, 3 Workers...")
    worker_pool = WorkerPool(3)

    print("Activating 1 Worker...")
    worker_1 = worker_pool.activate_worker()

    print("Deactivating 1 Worker...")
    worker_pool.deactivate_worker(worker_1)

    print("Active Worker Size:", worker_pool.active_count)

    print("Resizing pool 4 Workers...")
    worker_pool.resize(4)

    print("Total Workers: ", len(worker_pool._workers) + worker_pool.active_count)

    print("Resizing pool 2 Workers...")
    worker_pool.resize(2)

    print("Total Workers: ", len(worker_pool._workers) + worker_pool.active_count)