If you are willing to give external libraries a shot, you can express tasks and their dependencies elegantly with Ray. This works well on a single machine, the advantage here is that parallelism and dependencies can be easier to express with Ray than with python multiprocessing and it doesn't have the GIL (global interpreter lock) problem that often prevents multithreading from working efficiently. In addition it is very easy to scale the workload up on a cluster if you need to in the future.
The solution looks like this:
import ray
ray.init()
@ray.remote
def task1():
pass
@ray.remote
def task2():
pass
@ray.remote
def task3():
pass
@ray.remote
def dependent1(x1, x2, x3):
pass
@ray.remote
def task4():
pass
@ray.remote
def task5():
pass
@ray.remote
def task6():
pass
@ray.remote
def dependent2(x1, x2, x3):
pass
@ray.remote
def dependent3(x, y):
pass
id1 = task1.remote()
id2 = task2.remote()
id3 = task3.remote()
dependent_id1 = dependent1.remote(id1, id2, id3)
id4 = task4.remote()
id5 = task5.remote()
id6 = task6.remote()
dependent_id2 = dependent2.remote(id4, id5, id6)
dependent_id3 = dependent3.remote(dependent_id1, dependent_id2)
ray.get(dependent_id3) # This is optional, you can get the results if the tasks return an object
You can also pass actual python objects between the tasks by using the arguments inside of the tasks and returning the results (for example saying "return value" instead of the "pass" above).
Using "pip install ray" the above code works out of the box on a single machine, and it is also easy to parallelize applications on a cluster, either in the cloud or your own custom cluster, see https://ray.readthedocs.io/en/latest/autoscaling.html and https://ray.readthedocs.io/en/latest/using-ray-on-a-cluster.html). That might come in handy if your workload grows later on.
Disclaimer: I'm one of the developers of Ray.