os.fork()后的共享对象

1 投票
1 回答
2303 浏览
提问于 2025-04-18 04:52

我在使用多个进程与数据库交互时,遇到了一些奇怪的应用行为。我是在Linux系统上进行操作。

我自己实现了一个叫做QueryExecutor的东西,它在创建后会一直使用同一个连接:

class QueryExecutor(object):
    def __init__(self, db_conf):
        self._db_config = db_conf
        self._conn = self._get_connection()

    def execute_query(self, query):
        # some code
    # some more code

def query_executor():
    global _QUERY_EXECUTOR
    if _QUERY_EXECUTOR is None:
        _QUERY_EXECUTOR = QueryExecutor(some_db_config)
    return _QUERY_EXECUTOR

这个QueryExecutor在创建后不会被修改。

一开始只有一个进程,这个进程会不时地进行多次分叉(os.fork())。新创建的进程是工作进程,它们会执行一些任务然后退出。每个工作进程都会调用query_executor()来执行SQL查询。

我发现SQL查询经常返回错误的结果(有时查询的结果会返回给错误的进程)。唯一合理的解释是所有进程共享同一个SQL连接(根据MySQLdb的文档:线程安全 = 1 线程可以共享模块,但不能共享连接)。

我想知道是什么操作系统机制导致了这种情况。根据我所知,在Linux中,当一个进程分叉时,父进程的页面不会被复制给子进程,它们会被两个进程共享,直到其中一个尝试修改某个页面(这叫做写时复制)。正如我之前提到的,QueryExecutor对象在创建后保持不变。我猜这就是所有进程使用同一个QueryExecutor实例,从而共享同一个SQL连接的原因。

我这样理解对吗?还是我漏掉了什么?你有什么建议吗?

提前谢谢你!

Grzegorz

1 个回答

5

问题的根本在于,fork() 这个函数只是简单地创建了一个完全独立的进程副本,但这两个进程会 共享打开的文件、套接字和管道。所以,如果MySQL服务器写入的数据只能被一个进程正确读取,而如果两个进程同时尝试发请求和读取响应,它们很可能会互相干扰,导致出错。这和“多线程”没有关系,因为在多线程的情况下,只有一个进程,但有几个执行线程,它们共享数据并可以进行协调。

正确使用 fork() 的方法是,在创建进程副本后,立即 关闭(或者重新打开)所有文件句柄类的对象,除了其中一个进程副本,或者至少避免多个进程同时使用它们。

撰写回答