我正在构建一个多线程程序,因为它有大量的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
答案 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
个项目。