我在使用许多进程与数据库交互时遇到了一些奇怪的应用程序行为。我正在使用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
。
最初只有一个进程,有时会分叉(os.fork()
)几次。新流程是执行某些任务然后退出的工作人员。每个工作程序都调用query_executor()
来执行SQL查询。
我发现sql查询经常返回错误的结果(似乎有时sql查询结果返回错误的进程)。唯一明智的解释是所有进程共享相同的sql连接(根据MySQLdb doc: threadsafety = 1线程可以共享模块,但不能连接)。
我想知道哪种操作系统机制会导致这种情况。据我所知,在Linux处理进程分支时,父进程的页面不会被复制用于子进程,它们由两个进程共享,直到其中一个进程尝试修改某个页面( copy-on -write )。正如我之前提到的,QueryExecutor
对象在创建后仍未修改。我想这就是所有进程使用相同的QueryExecutor
实例并因此使用相同的sql连接这一事实的原因。
我是对的还是我错过了什么?你有什么建议吗?
提前致谢!
的Grzegorz
答案 0 :(得分:4)
问题的根源是fork()
只是创建了一个完全独立的进程副本,但是这两个进程share opened files, sockets and pipes。这就是为什么MySQL服务器编写的任何数据可能[正确]只能从单个进程读取,如果两个进程尝试发出请求并读取响应,那么它们很可能会混淆彼此的工作。这与"多线程"没有任何关系。因为在多线程的情况下,单个进程只有很少的执行线程,它们共享数据并可以协调。
使用fork()
的正确方法是在除了进程的一个副本之外的所有文件句柄类似对象之后立即close(或重新打开),或者至少避免使用它们来自多个过程。