在scoped_session

时间:2017-06-21 13:44:01

标签: python session sqlalchemy python-multiprocessing

我正在使用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>

现在会话实例不同了。

2 个答案:

答案 0 :(得分:1)

要了解发生这种情况的原因,您需要了解scoped_sessionPool实际上做了什么。 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