锁定对资源的访问权限的方式是最pythonic /正确的?

时间:2014-11-07 05:55:33

标签: python multithreading locking

我正在构建一个多线程程序,因为它有大量的I / O.我想构建一个队列,该队列不断地填充来自数据库的一些部分信息,并在它变低时补充它。在循环结束时,所有线程将检查队列是否需要补充。其中哪一个是最准确的(请注意它的pseduo代码,因为我只对更高级别的概念感兴趣)。

另外,请原谅我,我在命名空间方面也有点挣扎,正如您可能从代码中看到的那样。我对此进行了大量研究,经过了大量测试,而且我还在苦苦挣扎,所以非常感谢任何帮助!

# Option 1 - lock within the thread
class Thread():
    ...
    def run():
        # use an item off the queue
        with lock:
            replenish_queue()

lock = threading.Lock()
def replenish_queue():
    #check if queue needs replenishing

# ----------------------------------------

# Option 2 - lock within function
class Thread():
    ...
    def run():
        # use an item off the queue
        replenish_queue():

lock = threading.Lock()
def replenish_queue():
    with lock:
        #check if queue needs replenishing

1 个答案:

答案 0 :(得分:1)

由于大量并发数据库写入,我认为您的主要瓶颈之一实际上可能是 sqlite 本身。写锁定发生在数据库级别的 sqlite http://www.sqlite.org/faq.html)。

这与其他 RDBMS 相反,例如 Postgres ,它们在表级别提供锁定以进行更新,与结合使用MVCC ,就更新而言,表示UPDATE到一个表,它将锁定它以进行写入,不会阻止其他SELECT运行。

因此,无论您是通过线程还是多个进程完成它,尝试同时写入相同的 sqlite 数据库本质上都是一个串行进程,因为 sqlite 要求在写入期间在整个DB上取出写锁定。

但是,您可以在 sqlite 数据库上进行并发读取。因此,如果您的使用模式是这样的,您可以执行一堆读取,处理它们(从内部队列或类似),然后按顺序执行写入(因为 sqlite 基本上在数据库上强制执行互斥锁无论如何),这可能是最佳的。

就队列本身而言,John Mee提到的生成器绝对是一种选择,根据您的确切数据需求可能是最好的。

然而,另一个值得考虑的是将数据库本身用作队列 - SQL表通常很擅长;我一直在 Postgres 中做这件事,我怀疑他们在 sqlite 中的表现同样出色。如果您的需求位于一个表中,或者可以轻松地合并到一个队列中,这当然最有效。如果您的要求包括大量不容易JOIN的不同表格,或者来自不同数据库的表格,则需要在它们之间存在 ETL 层才能将其填入排队表格,这可能不值得。

如果使用您的 sqlite 库(例如,sqlite3),它确实很适合使用数据库作为队列,那么您将声明cursor并且可以使用它作为迭代器逐个检查,或fetchmany size参数等同于您想要一次处理的批处理 - 让我们说100。

然后,当主线程获取下一个块时,您可以将其包装到另一个进程(即使用multiprocessing)来完成工作。不过,首先要对每个块进行单个线程处理,这可能是值得的 - 修改提取的块大小以查看是否有帮助,因为通常它是实时花费的数据库交互的时间 - - 并查看主线程是否可以在可接受的时间内自行鞭打。如果没有,那么你可以随时将它分发给一群工人,然后加入他们。

当光标用完要获取的块时,你已经完成了主要的读取工作,而分叉的进程(或者如果你正在进行那条路径的主要进程)正在进行整个处理,并且一次这些都已完成,然后你可以在主线程中连续更新它们。

有时候需要多个线程/进程,特别是在进行大量I / O时,但情况并非总是如此 - 有时计划好/分块的数据库提取可以帮助大量工作并且不需要添加多线程/多处理的额外复杂性 - 所以我建议从那开始,只有在实际观察到的性能指示时才从那里构建。

修改

我从其中一条评论中看到您已经使用数据库进行排队。如果这意味着我在上面谈论的内容,那么我认为分批获取 - 并且修补以找到最佳大小 - 应该有助于提高性能(减少往返DB =减少开销,即使使用本地数据库) - - 并且肯定会有助于内存消耗,因为如果您在队列中有1000万个项目,那么您一次只能获取size个项目。