【问题标题】:Peewee, SQLite and threadingPeewee、SQLite 和线程
【发布时间】:2015-07-23 01:22:23
【问题描述】:

我正在开发一个线程应用程序,其中一个线程将向Queue 提供要修改的对象,然后许多其他线程将从队列中读取,进行修改并保存更改。

应用程序不需要很多并发,所以我想坚持使用 SQLite 数据库。这是一个说明应用程序的小示例:

import queue
import threading
import peewee as pw

db = pw.SqliteDatabase('test.db', threadlocals=True)

class Container(pw.Model):
    contents = pw.CharField(default="spam")

    class Meta:
        database = db


class FeederThread(threading.Thread):

    def __init__(self, input_queue):
        super().__init__()

        self.q = input_queue

    def run(self):
        containers = Container.select()

        for container in containers:
            self.q.put(container)


class ReaderThread(threading.Thread):

    def __init__(self, input_queue):
        super().__init__()

        self.q = input_queue

    def run(self):
        while True:
            item = self.q.get()

            with db.execution_context() as ctx:
                # Get a new connection to the container object:
                container = Container.get(id=item.id)
                container.contents = "eggs"
                container.save()

            self.q.task_done()


if __name__ == "__main__":

    db.connect()
    try:
        db.create_tables([Container,])
    except pw.OperationalError:
        pass
    else:
        [Container.create() for c in range(42)]
    db.close()

    q = queue.Queue(maxsize=10)


    feeder = FeederThread(q)
    feeder.setDaemon(True)
    feeder.start()

    for i in range(10):
        reader = ReaderThread(q)
        reader.setDaemon(True)
        reader.start()

    q.join()

基于 peewee 文档,SQLite 应该支持多线程。但是,我不断收到臭名昭著的peewee.OperationalError: database is locked 错误,错误输出指向container.save() 行。

我该如何解决这个问题?

【问题讨论】:

    标签: python multithreading sqlite peewee


    【解决方案1】:

    看到这个失败也让我有点惊讶,所以我复制了你的代码并尝试了一些不同的想法。我认为问题在于,ExecutionContext() 默认情况下会导致包装块在事务中运行。为了避免这种情况,我在读者线程中传递了False

    我还编辑了 feeder 以在将内容放入队列之前使用 SELECT 语句 (list(Container.select()))。

    以下内容在本地对我有用:

    class FeederThread(threading.Thread):
    
        def __init__(self, input_queue):
            super(FeederThread, self).__init__()
    
            self.q = input_queue
    
        def run(self):
            containers = list(Container.select())
    
            for container in containers:
                self.q.put(container.id)  # I don't like passing model instances around like this, personal preference though
    
    class ReaderThread(threading.Thread):
    
        def __init__(self, input_queue):
            super(ReaderThread, self).__init__()
    
            self.q = input_queue
    
        def run(self):
            while True:
                item = self.q.get()
    
                with db.execution_context(False):
                    # Get a new connection to the container object:
                    container = Container.get(id=item)
                    container.contents = "nuggets"
                    with db.atomic():
                        container.save()
    
                self.q.task_done()
    
    if __name__ == "__main__":
    
        with db.execution_context():
            try:
                db.create_tables([Container,])
            except OperationalError:
                pass
            else:
                [Container.create() for c in range(42)]
    
        # ... same ...
    

    我对此并不完全满意,但希望它能给你一些想法。

    这是我不久前写的一篇博文,其中包含一些使用 SQLite 获得更高并发性的技巧:http://charlesleifer.com/blog/sqlite-small-fast-reliable-choose-any-three-/

    【讨论】:

    • 感谢您的回答!线程对我来说仍然有点巫术,所以我很高兴这不是我的明显错误。有趣的是,使用 SELECT 语句似乎是这里的关键——我看不出使用 db.execution_context(False)with db.atomic() 有什么区别。事实上,通过使用 SELECT 语句,我什至不需要ExecutionContext()。所以我想 SELECT 语句实际上是在锁定数据库?
    【解决方案2】:

    你试过 WAL 模式吗?

    Improve INSERT-per-second performance of SQLite?

    如果您可以并发访问 SQLite,则必须非常小心,因为当写入完成时,整个数据库都会被锁定,尽管可以有多个读取器,但写入将被锁定。随着在较新的 SQLite 版本中添加 WAL,这已有所改善。

    如果你使用多线程,你可以尝试使用共享页面缓存,这将允许加载的页面在线程之间共享,这样可以避免昂贵的 I/O 调用。

    【讨论】:

    • 感谢您的建议!不幸的是,无论是 WAL 还是使用共享缓存,似乎都没有什么不同。
    猜你喜欢
    • 2010-10-06
    • 2014-03-22
    • 2013-12-06
    • 1970-01-01
    • 2022-01-16
    • 2018-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多