特定のタスクを実行する必要のあるフレームワークを作成しています。この例として、自動車の製造を使用します。したがって、個々のタスクは_weld metal
_または_screw bolt
_のようなものになります。しかし、タスクのコレクションもあります。私はそれらをjobsと呼びます。各ジョブはX個のタスクを持つことができるため、これらの2つのタスクを組み合わせると、ジョブ__build an engine
_を取得できます。
_build an engine
+-- weld metal
+-- screw bolts
_
さらに、ジョブはサブジョブを持つことができます:
_build a car
+-- build an engine
| +-- weld metal
| +-- screw bolts
+-- build a frame
| +-- weld metal
| +-- screw bolts
| +-- Paint
+-- combine engine and frame
_
つまり、基本的に私のツリーの各ノードはジョブであり、各リーフはタスクです。これは非常に簡単です。クラスは次のようになります。
_class Task:
def do(self):
raise NotImplementedError('abstract method')
class Job(Task):
def __init__(self):
self.tasks = []
def do(self):
for task in self.tasks:
task.do()
_
そして今、私はWeldMetal
からScrewBolts
とTask
をサブクラス化するだけです。
これを並列化したいときに問題が発生します。どういうわけか、各タスク(およびジョブ)が実行をブロックすべきか、それともブロックせずに別のスレッドで実行すべきかを伝える必要があります。時々私の仕事は他の仕事からの結果も必要としますが、それらの仕事は互いに並行して実行されるかもしれません。たとえば、上の自動車の建物の場合:
weld metal
_はブロックする必要があります。build an engine
_および_build a frame
_は、独自のスレッドで実行できます(これらのスレッド内では、_weld metal
_および_screw bolts
_は特定のスレッドをブロックする必要があります)。combine engine and frame
_は、_build an engine
_および_build a frame
_が完了するまで待機する必要があります。正直なところ、どこから始めればよいかわかりません。最初はThreadPoolExecutor
をJob.do()
で使用することを考えましたが、一部のジョブのみをブロックし、他のジョブをブロックしない方法や、別の問題についてはわかりませんつまり、タスクは、ジョブの内部に存在することなく「単独」で実行する必要がある場合があります。つまり、WeldMetal(block=False).do()
が有効である必要があります。
私はこれを最初に使用してしまいました:
_class Task:
def do(self):
raise NotImplementedError('abstract method')
def run(self):
if self.block:
self.do()
else:
threading.Thread(target=self.do).start()
_
そして、これはほとんどの場合に機能しますが、_combine engine and frame
_が_build an engine
_と_build a frame
_の両方が完了するのを待つ必要があるときに問題が発生します。
2つの懸念を1つの解決策にまとめようとしていますが、その解決策では実際には両方の懸念をカバーできないため、問題が発生しています。
一方で、ブロッキングタスク(同じジョブの他のタスクと並行して実行されない可能性があるタスク)があり、他方で、タスク間に依存関係があります(タスクは、1つ以上の他の特定のタスクが完了するまで開始できません。完了しました)。
ジョブの最初のタスクであるタスクをブロックするためのソリューションがありますが、依存関係や、非ブロックタスクの後に来るタスクをブロックするためのソリューションがあります(Paint
がブロックしている場合でも、実行できます) _screw bolts
_ジョブの一部として_build a frame
_と並行して)。
ブロッキングと依存関係の両方を処理するには、次のようなものを使用できます。
_class Task:
def __init__(self):
self._done = threading.Event()
self.dependencies = []
def wait_for_completion(self):
self._done.wait()
def do(self):
raise NotImplementedError('abstract method')
def do_async(self):
for depends in self.dependencies:
depends.wait_for_completion()
self.do()
self._done.set()
def run(self):
threading.Thread(target=self.do_async).start()
class Job(Task):
def __init__(self):
super(Job, self).__init__()
self.tasks = []
def do(self):
started_tasks = []
for task in self.tasks:
if task.block:
self._wait_for_tasks(started_tasks)
started_tasks = []
else:
started_tasks.append(task)
task.run()
if task.block:
task.wait_for_completion()
self._wait_for_tasks(self.tasks)
def _wait_for_tasks(self, task_list):
for task in task_list:
task.wait_for_completion()
_
Task
クラスは、依存関係によってイベントが設定されるまでタスクのトレッドをブロックすることにより、依存関係の処理を行います。 Job
クラスを拡張して、そのジョブ内の他のタスクが実行されていない場合にのみブロックタスクが実行されるようにし、含まれているすべてのタスクが完了するまでジョブが完了として報告されないようにしました。その最後の部分は、依存関係メカニズムがジョブで正しく機能するために必要です。
スレッドをjoin()
ingするのではなく、意図的にEvent
オブジェクトを同期に使用しました。これにより、タスクAがタスクBの前にタスクBで待機し始める場合のコードがより堅牢になります。始まった。タスクとジョブが順序付けられたシーケンスを形成する場合(単一スレッドで指定された順序ですべてを実行しても、依存関係に違反しない場合)に限り、コードはデッドロックがないことが保証されます。
asyncio は、あなたが望むことのほとんどをすでに行っているようです。例えば:
_import asyncio
async def build_a_car():
engine_task = asyncio.create_task(build_an_engine())
frame_task = asyncio.create_task(build_a_frame())
engine = await engine_task
frame = await frame_task
await combine_engine_and_frame(engine, frame)
async def build_an_engine():
await weld_metal()
await screw_bolts()
async def build_a_frame():
await weld_metal()
await screw_bolts()
await Paint()
asyncio.run(build_a_car())
_
asyncio.run(weld_metal())
を使用して、コルーチンを単独で実行できます。階層の最上部について特別なことは何もありません。
それは私が始める場所であり、それはあなたにかなりの道を開くはずです。より重いものを探している場合は、 Celery または Kubernetes Jobs のような分散タスクキューを見ることができますが、これらは多くのコンピューティングリソースを依存関係のシーケンスを簡単にするのではなく、信頼できる方法です。