#python #multithreading #concurrency #sqlite
#python #многопоточность #параллелизм #sqlite
Вопрос:
Для полного использования параллелизма SQLite3 позволяет потокам получать доступ к одному и тому же соединению тремя способами:
- Однопоточный. В этом режиме все мьютексы отключены, и SQLite небезопасно использовать более чем в одном потоке одновременно.
- Многопоточность. В этом режиме SQLite может безопасно использоваться несколькими потоками при условии, что ни одно подключение к базе данных не используется одновременно в двух или более потоках.
- Сериализованный. В сериализованном режиме SQLite может безопасно использоваться несколькими потоками без каких-либо ограничений.
Кто-нибудь знает, как я могу сериализовать соединение в Python.
В Python есть «check_same_thread», который позволяет переключаться между многопоточностью и однопоточностью; однако я не могу выяснить, как я должен сделать это сериализованным.
Ответ №1:
Модуль Python SQLite не является потокобезопасным. Если вы отключите его проверку, то вам нужно убедиться, что весь код сериализован, и это включает сборку мусора. (Мой модуль APSW является потокобезопасным, а также корректно обрабатывает сообщения об ошибках потокобезопасности).
Однако безопасно использовать несколько независимых подключений одновременно в одном процессе, и я бы рекомендовал вам сделать это. Дополнительно переключите базу данных в режим ведения журнала с опережением записи, и вы должны получить очень хорошую производительность даже при большом количестве записей.
Ответ №2:
Я написал библиотеку для решения этой проблемы. У меня работает.
Ответ №3:
Страница sqlite http://www.sqlite.org/threadsafe.html говорится: «Режим по умолчанию — сериализованный». Вы тестировали это и обнаружили, что это неверно?
Редактировать:
Если это не сработает, может быть, ctypes? Я понятия не имею, повлияет ли это как-либо на загруженный модуль sqlite. Думаю, я как бы подозреваю, что это не так; как я предполагаю, sqlite3_initialize()
функция, скорее всего, вызывается при загрузке модуля? Или, может быть, только при создании объекта базы данных?
http://www.sqlite.org/c3ref/config.html
>>> import sqlite3
>>> import ctypes
>>> from ctypes.util import find_library
>>> sqlite_lib = ctypes.CDLL(find_library('sqlite3'))
>>> sqlite_lib.sqlite3_config(3) # http://www.sqlite.org/c3ref/c_abort.html
0 # no error....
>>>
Комментарии:
1. Да, я сделал. Единственный способ переключаться между ними — использовать «check_same_thread». Когда значение check_same_thread равно true, он работает в однопоточном режиме, а когда оно равно false, он переключается в многопоточный режим. Не совсем уверен, как это сделать в сериализованном режиме.
Ответ №4:
Из Verse Quiz вас могут заинтересовать __init__
, __serve
и __fetch
методы, которые помогут вам начать создание сериализованного интерфейса базы данных SQLite3 на Python. Надеюсь, это поможет вам в дальнейшем!
import _thread
import sqlite3
import queue
################################################################################
class Server:
"""Execute a protected SQLite3 database on a singular thread.
Since a SQLite3 database can only accept queries on the thread that it
was created on, this server receives requests through a queue and sends
back the result through a list and mutex mechanism."""
def __init__(self, *args):
"""Initialize the Server with a SQLite3 database thread."""
self.__lock = _thread.allocate_lock()
self.__lock.acquire()
_thread.start_new_thread(self.__serve, args)
self.__lock.acquire()
del self.__lock
if self.__error is not None:
raise self.__error
del self.__error
def __serve(self, *args):
"""Run a server continuously to answer SQL queries.
A SQLite3 connection is made in this thread with errors being raised
again for the instantiator. If the connection was made successfully,
then the server goes into a continuous loop, processing SQL queries."""
try:
database = sqlite3.connect(*args)
except:
self.__error = error = sys.exc_info()[1]
else:
self.__error = error = None
self.__lock.release()
if error is None:
self.__QU = queue.Queue()
while True:
lock, one, sql, parameters, ret = self.__QU.get()
try:
cursor = database.cursor()
cursor.execute(sql, parameters)
data = cursor.fetchone() if one else cursor.fetchall()
ret.extend([True, data])
except:
ret.extend([False, sys.exc_info()[1]])
lock.release()
def fetch(self, one, sql, *parameters):
"""Execute the specified SQL query and return the results.
This is a powerful shortcut method that is the closest connection
other threads will have with the SQL server. The parameters for the
query are dumped into a queue, and the answer is retrieved when it
becomes available. This prevents SQLite3 from throwing exceptions."""
lock, ret = _thread.allocate_lock(), []
lock.acquire()
self.__QU.put((lock, one, sql, parameters, ret))
lock.acquire()
if ret[0]:
return ret[1]
raise ret[1]
Комментарии:
1. Я уверен, что есть лучший способ обработки потоков, если вы используете
threading
модуль.2. К сожалению, поскольку приложение много пишет в разных потоках, любое решение приведет к снижению производительности. Вместо этого я переключился на Mysql. Однако спасибо за комментарии.
3. Не могли бы вы добиться того же эффекта, что и в этом коде, установив check_same_thread=False , а затем просто используя единственную блокировку, чтобы гарантировать, что база данных используется только одним потоком одновременно? (Когда дело доходит до параллелизма, чем проще, тем лучше).