我正在使用SQLAlchemy和多处理。我也使用scoped_session sinse它避免共享相同的会话但我发现了一个错误和他们的解决方案,但我不明白为什么会发生。
您可以在下面看到我的代码:
db.py
engine = create_engine(connection_string)
Session = sessionmaker(bind=engine)
DBSession = scoped_session(Session)
script.py
from multiprocessing import Pool, current_process
from db import DBSession
def process_feed(test):
session = DBSession()
print(current_process().name, session)
def run():
session = DBSession()
pool = Pool()
print(current_process().name, session)
pool.map_async(process_feed, [1, 2]).get()
if __name__ == "__main__":
run()
当我运行script.py
输出为:
MainProcess <sqlalchemy.orm.session.Session object at 0xb707b14c>
ForkPoolWorker-1 <sqlalchemy.orm.session.Session object at 0xb707b14c>
ForkPoolWorker-2 <sqlalchemy.orm.session.Session object at 0xb707b14c>
请注意,主进程中的会话对象与0xb707b14c
及其工作人员(子进程)相同
但如果我改变前两行的顺序run():
def run():
pool = Pool() # <--- Now pool is instanced in the first line
session = DBSession() # <--- Now session is instanced in the second line
print(current_process().name, session)
pool.map_async(process_feed, [1, 2]).get()
然后我再次运行script.py
输出:
MainProcess <sqlalchemy.orm.session.Session object at 0xb66907cc>
ForkPoolWorker-1 <sqlalchemy.orm.session.Session object at 0xb669046c>
ForkPoolWorker-2 <sqlalchemy.orm.session.Session object at 0xb66905ec>
现在会话实例不同了。
答案 0 :(得分:1)
要了解发生这种情况的原因,您需要了解scoped_session
和Pool
实际上做了什么。 scoped_session
保留会话注册表,以便发生以下情况
DBSession
时,它会在注册表中为您创建一个Session
对象Session
对象,而是返回先前创建的Session
对象创建Pool
时,它会使用__init__
方法创建工作人员。 (请注意,在__init__
中启动工作进程没有任何基础。同样有效的实现可能要等到工作人员在启动它们之前首次需要,这会在您的示例中表现出不同的行为。)发生(在Unix上),每个工作进程的父进程 forks 本身,它涉及操作系统将当前正在运行的进程的内存复制到一个新进程中,因此您将从字面上获得完全相同的对象在完全相同的地方。
将这两者放在一起,在第一个示例中,您在分叉之前创建Session
,在创建Pool
期间将其复制到所有工作进程,从而产生相同的身份,同时在第二个示例中,您将延迟创建Session
对象,直到工作进程启动,从而产生不同的身份。
值得注意的是,虽然Session
个对象共享相同的id
,但它们不是同一个对象,因为如果您更改有关父进程中Session
的任何内容,它们都不会反映在子进程中。由于fork,它们碰巧都共享相同的内存地址。 然而,连接等操作系统级资源是共享的,因此如果您在session
之前在Pool()
上运行了查询,那么连接就是在连接池中为您创建,然后分叉到子进程中。如果您然后尝试在子进程中执行查询,那么您将遇到奇怪的错误,因为您的进程在同一个连接上相互冲突!
以上对Windows来说没有意义,因为Windows没有fork()
。
答案 1 :(得分:0)
TCP连接以文件描述符表示,它们通常跨进程边界工作,这意味着它将代表两个或多个完全独立的Python解释器状态并发访问文件描述符。
https://docs.sqlalchemy.org/en/13/core/pooling.html#using-connection-pools-with-multiprocessing