如何通过多进程共享(或排除共享)全局变量?
Posted
技术标签:
【中文标题】如何通过多进程共享(或排除共享)全局变量?【英文标题】:How to share (or exclude from sharing) global variables over multiprocess? 【发布时间】:2021-02-09 06:09:52 【问题描述】:在 Linux 中,我理解每当您派生子进程时,子进程都会获得父进程地址空间的副本。
我不明白为什么这总是可取的。父进程可能具有不应在父进程和子进程之间共享的全局对象不。为什么我们希望子进程始终获取它们?
例如,假设父进程维护一个全局数据库客户端。
client.py
client = None
def get_client():
global client
if client is None:
client = make_new_db_client()
return client
这在我的单进程应用程序中运行良好。但是一旦我开始使用多处理就可能会中断。
问题是我正在跨进程共享db_client
对象。这个数据库代码实际上是一个 3rd 方库。让我们假装它根本不能跨多进程工作。所以,我在父进程中创建了一个db_client
,并强制子进程使用它。会失败的。
我认为正确的做法是允许用户有选择地选择跨多进程共享的内容。所以在这种情况下,我根本不会共享 db_client。子进程将启动并创建自己的数据库连接。一切都会好起来的。
但这不是 Python 的设计方式。全局变量总是被复制(至少在 Linux 中)。为什么这样做是可取的?这似乎有时很好,但并非总是如此。
或者是否已经有一种方法可以选择性地对子进程隐藏变量?
【问题讨论】:
这是一个非常广泛的问题,与 Python 无关。它是关于进程如何在大多数现代操作系统上工作的。有很多方法可以控制这一点,例如在创建其他变量之前/之后分叉,或者使用管道等 IPC 机制在进程之间进行通信。这完全取决于您的应用程序,但是由于您正在编写它,因此您大概可以选择适合您需求的东西。那是什么? @bnaecker - 感谢您的评论。是的,这是一个相当广泛的问题。至于我的需求,我从一个进行数据库查询的应用程序开始。多进程元素是当我想使用 concurrent.futures.executor 来获得更好的性能时。我不知道执行程序是先运行还是我的主进程先运行。这用于我的研究环境,因此对事件的顺序没有严格的要求。我只想让执行者成为我的主流程的延伸。 您的应用程序主要是在查询数据库?除非您正在执行 大量 查询,否则添加更多线程或进程不太可能有所帮助。这是教科书式的 IO-bound 工作,应用程序将主要等待这些请求完成。但是,如果您确实想要并行化工作,那么从完全独立的进程开始通常是一个好主意,这些进程在需要时通信,而不是共享状态。multiprocessing
模块提供了许多用于在进程之间发送数据的工具。
【参考方案1】:
不一定总是可取的。在许多情况下,它是无关紧要的,在某些情况下令人困惑。许多并行编程错误与发散变量和开发人员假设它们是同步的有关。
愿望与这种设计关系不大。在 Linux(和大多数其他 Unix 变体)中,Python 解释器通过在 C 级别调用系统调用 fork()
创建一个子进程。这会创建一个(几乎)相同的调用 fork 的进程的副本。有一些进程绑定的资源,例如定时器,不被子进程继承,但变量是。
这是传统的 Unix 创建进程的方式,它有一些好处
实现(对于那些已经开发过 Python 的人)是由一个经受住时间考验的系统调用提供的。 速度很快。其中包含的逻辑很少 - 内存只是复制到另一个位置 子进程是可行的。在您的问题中描述的理论混合与匹配模型中,可以创建一个缺少重要组件的子流程 这提供了一种将数据传递给子进程的快速方法考虑一个不会这样做的子进程。这将是一个白板。默认情况下,一个空核心能够稍后以某种方式从创建它的父级接收可执行代码和参数。
要使它成为一个运行且有用的组件,需要做更多的工作。在实施中(您需要考虑每个细节)和执行。传输数据需要进程之间的通信。从理论上讲,将进程状态初始化到您想要的位置可能需要大量工作,例如从数据库中读取配置参数。如果您需要启动数千个子流程,那么在每个流程中重复这项工作会慢得多。
当然,在某些情况下,继承全局变量会使您误入歧途。出于这个原因,一些开发人员会避开全局变量。外部进程或服务的连接器需要在子进程中重新创建,如果全局变量代表这些,您的程序可能会变得混乱。
并行编程需要良好的设计和对细节的高度关注的原因之一正是这一点 - 没有编程语言可以猜测您的程序想要在子进程中做什么。输入和输出是什么?它是否需要与父进程或其他进程或计算机通信?这是怎么发生的?等等。
完全有可能并行处理不适合您的任务。在很多情况下,由于应用程序的功能,子流程根本无法帮助您。在某些情况下,可以通过重新设计应用程序来解决明显的多处理相关问题。有时在主进程中处理外部通信并且只将计算等分派给子进程可能是明智的。在另一种情况下,让主程序什么都不做或几乎什么都不做,让每个子进程独立订阅外部源并直接传递结果可能是理想的。许多服务器端应用程序都以这种方式工作。
对于一个几乎没有答案的冗长回答表示歉意。
【讨论】:
感谢您的回答。即使我不使用全局变量,难道我还是有同样的问题吗?假设我有一个管理器类来跟踪这些数据库连接。我碰巧在我的主进程中初始化了管理器。现在,当我生成子进程时,整个虚拟内存都会被复制。所以我的管理器实例也被复制到子进程中。管理器实例现在将具有在子进程中无用的数据库连接,对吗?所以问题不在于全局变量。这是一个普遍的问题,对吧? 我很确定您不能在管理器中传递数据库连接。管理器使用更简单的对象。您是否有理由无法在子进程中初始化数据库连接?这是通常的做法。 另一种避免将不需要的对象复制到子进程的方法(例如出于内存原因 - 如果子进程不需要它们,复制大量结构毫无意义)是在初始化数据库之前生成子进程连接并在父级中加载大量结构。您可以在实际需要子流程之前启动它们。 你是对的。我不想在进程之间传递数据库连接。我想在子进程中初始化连接。但是我该怎么做呢?如何在子进程中初始化连接?现在,数据库初始化发生在访问之前,并且连接存储在映射中。首次访问时地图为空,因此我初始化了一个连接并将其存储在地图中。所有其他访问都将在地图中看到现有连接,因此它会重用它。这在单进程中有效,但在多进程中中断,因为映射被复制到子进程,并且子进程会尝试使用它 关于在我需要之前启动子流程的第二条评论 - 我认为这并不总是适用于我的工作流程。我并不总是提前知道我是否需要子流程,因为我的工作是交互式的。当我希望 concurrent.futures.executor 并行化我的工作时,我使用子进程。我的工作是互动的,因为它是我研究环境的一部分。所以 1 天,我可能不需要多进程,第二天,我可能需要 4 个执行器。第二天,我可能需要 8 个。他们都可以坐在一个长时间运行的 jupyter notebook 实例中以上是关于如何通过多进程共享(或排除共享)全局变量?的主要内容,如果未能解决你的问题,请参考以下文章