Python 锁定对资源的访问的哪种方式最适合/正确?

Python 锁定对资源的访问的哪种方式最适合/正确?,python,multithreading,locking,Python,Multithreading,Locking,我正在构建一个多线程程序,因为它有大量的I/O。我想构建一个队列,该队列不断填充来自数据库的部分信息,并在数据不足时进行补充。在周期结束时,所有线程都将检查队列是否需要补充。以下哪一项是最准确的,请记住这是psedoo代码,因为我只对更高层次的概念感兴趣 另外,请原谅,正如您可能从代码中看到的那样,我在名称空间方面也有点困难。我已经对此进行了大量的研究和测试,但我仍在努力,因此非常感谢您的帮助 # Option 1 - lock within the thread class Thread():

我正在构建一个多线程程序,因为它有大量的I/O。我想构建一个队列,该队列不断填充来自数据库的部分信息,并在数据不足时进行补充。在周期结束时,所有线程都将检查队列是否需要补充。以下哪一项是最准确的,请记住这是psedoo代码,因为我只对更高层次的概念感兴趣

另外,请原谅,正如您可能从代码中看到的那样,我在名称空间方面也有点困难。我已经对此进行了大量的研究和测试,但我仍在努力,因此非常感谢您的帮助

# 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

对于大量并发数据库写入,我认为您的主要瓶颈之一实际上可能是sqlite本身。sqlite中的写锁发生在数据库级别

这与其他RDBMS(如Postgres)不同,后者为更新提供表级锁定,与MVCC结合使用,后者在更新方面意味着对表的更新(将其锁定以进行写入)不会阻止其他选择的运行

因此,无论您是通过线程还是通过多个进程来完成,尝试同时写入同一个sqlite数据库本质上是一个串行进程,因为sqlite要求在写入期间对整个数据库执行写入锁定

但是,您可以对sqlite数据库进行并发读取。因此,如果您的使用模式是这样的,您可以执行一系列读取,从内部队列或类似队列中处理它们,然后串行地执行写入,因为sqlite本质上在数据库上强制执行一个互斥锁以进行写入,那么这可能是最佳的

就队列本身而言,John Mee提到的生成器肯定是一种选择,并且可能是最佳选择,具体取决于您的确切数据需求

然而,另一个值得考虑的是使用数据库本身作为队列-SQL表通常擅长于此;我在Postgres中一直这样做,我怀疑它们在sqlite中也同样有效。当然,如果您的需求在一个表中,或者可以很容易地合并到一个队列中,那么这将是最有效的。如果您的需求包括大量不容易连接的不同表,或者来自不同数据库的表,那么在这两者之间需要有一个ETL层来将其放入队列形式,这在当时可能是不值得的

在这种情况下,使用DB作为队列非常合适,使用sqlite库(比如sqlite3),您可以声明游标,并可以将其作为迭代器逐个遍历,或者使用一个与您希望立即处理的批相当的大小参数fetchmany(比如100)

然后,您可以将其打包到另一个进程,即在主线程获取下一个块时,使用多进程来完成工作。不过,首先让一个线程处理每个数据块可能是值得的——修改获取的数据块大小,看看这是否有帮助,因为通常情况下,实时时间都花在DB交互上——看看主线程是否可以在可接受的时间内自己快速处理数据块。如果没有,那么你可以把它分给一群工人,然后再把他们联合起来

当光标用完要获取的数据块时,您就完成了主读取工作,而分叉进程或主进程(如果您要走这条路线)则一直在执行它们的处理,一旦这些都完成了,您就可以在主线程中连续更新它们

有时确实需要多个线程/进程,尤其是在执行大量I/O时,但情况并非总是如此——有时精心规划的/分块的数据库回迁可以帮助解决很多问题,避免增加多线程/多处理的额外复杂性——因此我建议从这一点开始,并仅在实际观察到的性能要求时才从这一点开始构建

编辑:


我从其中一条评论中看到,您已经在使用数据库排队了。如果你指的是我上面所说的,那么我认为分批获取-并修补以找到最佳大小-应该有助于减少性能往返到DB=减少开销,即使是本地DB-并且肯定会有助于内存消耗,因为,如果队列中有1000万项,您一次只能取大小的物品。

您读过GIL吗?你确定需要实现锁定吗?@IgnacioVazquez Abrams检查是通过对数据库的sqlite调用完成的,它一直在与非常小的并发级别进行斗争,因此我猜我需要将数据库调用与锁定同步,不是吗?Re:GIL,如果你还没有读到它,我强烈建议你这样做。简而言之,由于GIL,Python中的线程可能会…有问题。。。这就是为什么经常使用多个流程,其中每个流程都有自己的GIL,问题是有一个方面
奇怪-通常问题是清空队列,而不是填满队列。向数据库添加项目时是否存在您无法控制的外部因素?或者其他一些原因,你不能从一个包含所有内容的队列开始,或者一个由添加到数据库的同一进程添加到的队列?@JohnMee问得好,所以我只是构建一个刮板来检测站点是否有任何带有死链接的页面。所以,不仅仅是死链接,还有哪些页面有它们。它检查一个页面,然后将该页面中的链接排队,以搜索死链接。我设置了一个队列,然后我的内存爆炸了,回想起来。现在我把它们排在数据库的磁盘上。你知道我怎样才能做得更聪明吗?