跨越scoped_session的进程边界



我正在使用SQLAlchemy和多处理。我也使用scoped_session避免共享同一会话,但我发现了一个错误和他们的解决方案,但我不明白为什么会发生。

你可以在下面看到我的代码:

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>

现在会话实例不同了。

要理解为什么会发生这种情况,您需要了解scoped_sessionPool实际的作用。scoped_session保留会话注册表,以便发生以下情况

  • 第一次调用DBSession时,它会在注册表中为您创建一个Session对象
  • 随后,如果满足必要的条件(即同一线程,会话尚未关闭),它不会创建新的Session对象,而是返回您之前创建的Session对象

创建Pool时,它会在__init__方法中创建工作线程。(请注意,在__init__中启动工作进程没有任何基本意义。同样有效的实现可以等到首先需要辅助角色后再启动它们,这会在您的示例中表现出不同的行为。当这种情况发生时(在Unix上),父进程为每个工作进程分,这涉及操作系统将当前正在运行的进程的内存复制到一个新进程中,因此您将在完全相同的位置获得完全相同的对象。

将这两者放在一起,在第一个示例中,您将在分叉之前创建一个Session,该在创建Pool期间复制到所有工作进程,从而产生相同的标识,而在第二个示例中,您将Session对象的创建延迟到工作进程启动之后, 导致不同的身份。

请务必注意,虽然Session对象共享相同的id,但它们不是同一个对象,也就是说,如果您在父进程中更改有关Session的任何内容,它们将不会反映在子进程中。由于分叉,它们恰好都共享相同的内存地址。但是,操作系统级别的资源(如连接)共享的,因此,如果您在Pool()之前对session运行了查询,则会在连接池中为您创建一个连接,并随后分叉到子进程中。如果您随后尝试在子进程中执行查询,则会遇到奇怪的错误,因为您的进程在相同的确切连接上相互破坏!

以上内容对于Windows来说是没有意义的,因为Windows没有fork()

TCP 连接表示为文件描述符,通常跨进程边界工作,这意味着这将导致代表两个或多个完全独立的 Python 解释器状态并发访问文件描述符。

https://docs.sqlalchemy.org/en/13/core/pooling.html#using-connection-pools-with-multiprocessing

最新更新