金字塔:多线程数据库操作

时间:2014-07-11 19:41:19

标签: python multithreading pyramid

我的应用程序从用户接收一个或多个URL(通常为3-4个URL),从这些URL中抓取某些数据并将这些数据写入数据库。但是,因为抓取这些数据需要一段时间,我正在考虑在一个单独的线程中运行每个抓取,以便抓取+写入数据库可以继续在后台进行,这样用户就不必继续等待。

为了实现这一点,我有(仅限相关部分):

@view_config(route_name="add_movie", renderer="templates/add_movie.jinja2")
def add_movie(request):
    post_data = request.POST

    if "movies" in post_data:
        movies = post_data["movies"].split(os.linesep)

        for movie_id in movies:        
            movie_thread = Thread(target=store_movie_details, args=(movie_id,))
            movie_thread.start()

    return {}

def store_movie_details(movie_id):

    movie_details = scrape_data(movie_id)
    new_movie = Movie(**movie_details) # Movie is my model.

    print new_movie  # Works fine.

    print DBSession.add(movies(**movie_details))  # Returns None.

虽然行new_movie确实打印了正确的报废数据,但DBSession.add()不起作用。实际上,它只返回None

如果我删除线程并只调用方法store_movie_details(),它就可以正常工作。

发生了什么事?

2 个答案:

答案 0 :(得分:2)

首先,Session.add()上的SA文档没有提及该方法的返回值,因此我认为它应该返回None

其次,我认为您打算在会话中添加new_movie,而不是movies(**movie_details),无论是什么:)

第三,标准Pyramid会话(使用ZopeTransactionExtension配置的会话)与Pyramid的请求 - 响应周期相关联,这可能会在您的情况下产生意外行为。您需要配置一个单独的会话,您需要在store_movie_details中手动提交。此会话需要使用scoped_session,因此会话对象是线程本地的,不会跨线程共享。

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

session_factory = sessionmaker(bind=some_engine)
AsyncSession = scoped_session(session_factory)

def store_movie_details(movie_id):

    session = AsyncSession()
    movie_details = scrape_data(movie_id)
    new_movie = Movie(**movie_details) # Movie is my model.

    session.add(new_movie)
    session.commit()

当然,这种方法仅适用于非常轻量级的任务,如果您不介意偶尔丢失任务(例如,当Web服务器重新启动时)。对于任何更严肃的事情都要看看芹菜等,正如Antoine Leclair所暗示的那样。

答案 1 :(得分:0)

事务管理器在返回响应时关闭事务。返回响应时,DBSession在其他线程中没有事务。此外,跨线程共享事务可能不是一个好主意。

这是工人的典型用例。查看CeleryRQ