在使用多个进程与数据库交互时,我遇到了一些奇怪的应用程序行为。我正在使用 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
Query Executor
在实例化后永远不会被修改。
最初只有一个进程,它会时不时地 fork (os.fork()
) 几次。新进程是执行一些任务然后退出的 worker 。每个工作人员都调用 query_executor()
来执行 SQL 查询。
我发现sql查询经常返回错误的结果(好像有时候sql查询结果返回到错误的进程)。唯一合理的解释是所有进程共享相同的 sql 连接(根据 MySQLdb 文档:threadsafety = 1 线程可以共享模块,但不能共享连接)。
不知道是什么OS机制导致了这种情况。据我所知,在 Linux 上,当进程 fork 时,父进程的页面不会为子进程复制,它们由两个进程共享,直到其中一个尝试修改某些页面(写时复制).正如我之前提到的,QueryExecutor
对象在创建后保持不变。我想这就是所有进程使用相同的 QueryExecutor
实例并因此使用相同的 sql 连接的原因。
我是对的还是我错过了什么?你有什么建议吗?
提前致谢!
格热戈日
最佳答案
问题的根源在于fork()
只是创建了一个进程的完全独立的副本,但这两个进程share opened files, sockets and pipes .这就是为什么 MySQL 服务器写入的任何数据可能 [正确] 只能从单个进程读取,如果两个进程尝试发出请求并读取响应,那么它们很可能会相互干扰工作。这与“多线程”无关,因为在多线程的情况下,只有一个进程和很少的执行线程,它们共享数据并可以协调。
使用fork()
的正确方法是close (或重新打开)在除进程的一个副本之外的所有文件句柄类对象之后立即 fork ,或者至少避免在多个进程中使用它们。
关于python - os.fork() 之后的共享对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23385700/