多线程执行相互独立的模块

33 阅读3分钟

在编写一个由多个模块组成的 Python 脚本时,其中一些模块具有依赖关系,因此只有在依赖模块成功运行后才能运行这些模块。每个模块都继承自一个基本类模块,并重写名为 DEPENDENCIES 的列表,该列表包含在运行此模块之前需要满足的依赖关系。其中一个模块需要在所有其他模块之前运行。

目前,我们使用以下代码来实现上述功能:

modules_to_run.append(a)
modules_to_run.append(b)
modules_to_run.append(c)
.....
.....     
modules_to_run.append(z)


# Very simplistically just run the Analysis modules sequentially in
# an order that respects their dependencies
foundOne = True
while foundOne and len(modules_to_run) > 0:
    foundOne = False
    for module in modules_to_run:
        if len(module.DEPENDENCIES) == 0:
            foundOne = True
            print_log("Executing module %s..." % module.__name__, log)
            try:
                module().execute()
                modules_to_run.remove(module)
                for module2 in modules_to_run:
                    try:
                        module2.DEPENDENCIES.remove(module)
                    except:
                        #module may not be in module2's DEPENDENCIES
                        pass
            except Exception as e:
                print_log("ERROR: %s did not run to completion" % module.__name__, log)
                modules_to_run.remove(module)
                print_log(e, log)

for module in modules_to_run:
    name = module.__name__
    print_log("ERROR: %s has unmet dependencies and could not be run:" % name, log)
    print_log(module.DEPENDENCIES, log)

然而,我们发现某些模块需要花费很长时间才能执行,导致脚本运行时间过长。因此,我们希望通过多线程的方式来提高效率,让相互独立的模块同时运行,以节省时间。

2、解决方案

我们的目标是:在每次迭代中,重新计算一组相互独立的模块,然后以并行方式同时执行这些模块,等到这些模块都执行完毕再进行下一次迭代。为此,我们需要一个算法来找出这组独立的模块。

我们可以使用以下步骤来实现这一目标:

  1. 构建一个依赖关系图。
  2. 找到一组没有依赖关系的模块。这些模块可以现在并行执行。
  3. 将这些模块从依赖关系图中移除。
  4. 重复步骤 2,直到所有模块都完成。

我们可以参考拓扑排序算法来实现上述步骤。拓扑排序算法是一种将有向无环图转换为线性顺序的算法,可以用来解决各种问题,比如项目管理中的任务调度。

以下是拓扑排序算法的伪代码:

def topological_sort(graph):
    # 初始化一个空列表来存储排序后的模块
    sorted_modules = []

    # 初始化一个空列表来存储入度为 0 的模块
    no_dependencies = []

    # 遍历图中的每个模块
    for module in graph:
        # 如果模块的入度为 0,则将其添加到入度为 0 的模块列表中
        if len(graph[module]) == 0:
            no_dependencies.append(module)

    # 循环处理入度为 0 的模块
    while no_dependencies:
        # 从入度为 0 的模块列表中移除第一个模块
        module = no_dependencies.pop(0)

        # 将此模块添加到排序后的模块列表中
        sorted_modules.append(module)

        # 遍历此模块的邻居节点
        for neighbor in graph[module]:
            # 将此节点的入度减 1
            graph[neighbor].remove(module)

            # 如果此节点的入度为 0,则将其添加到入度为 0 的模块列表中
            if len(graph[neighbor]) == 0:
                no_dependencies.append(neighbor)

    # 返回排序后的模块列表
    return sorted_modules

然后,我们可以使用拓扑排序算法来找到一组相互独立的模块,并将这些模块并行执行。

以下是一个示例代码:

import threading

# 初始化一个依赖关系图
graph = {}
graph['A'] = ['B', 'C']
graph['B'] = ['D']
graph['C'] = ['E']
graph['D'] = []
graph['E'] = []

# 使用拓扑排序算法找到一组相互独立的模块
sorted_modules = topological_sort(graph)

# 创建一个线程列表来存储要执行的线程
threads = []

# 创建一个事件对象来等待所有线程完成
event = threading.Event()

# 遍历排序后的模块列表,并创建线程来执行每个模块
for module in sorted_modules:
    # 创建一个线程来执行此模块
    thread = threading.Thread(target=module.execute)

    # 将此线程添加到线程列表中
    threads.append(thread)

    # 启动此线程
    thread.start()

# 等待所有线程完成
event.wait()

# 所有线程都已完成,打印结果
print("All modules have been executed.")