В документации сказано, что пул соединений также не предназначен для многопоточности:
It’s critical that when using a connection pool, and by extension when using an Engine created via create_engine(), that the pooled connections are not shared to a forked process. TCP connections are represented as file descriptors, which usually work across process boundaries, meaning this will cause concurrent access to the file descriptor on behalf of two or more entirely independent Python interpreter states.
Насколько я понимаю, если я создам пул соединений:
self.engine = create_engine('postgresql://{user}:{password}@{host}:{port}/{db}'.format(
user=Configuration().get(section='repository', option='user'),
password=Configuration().get(section='repository', option='password'),
host=Configuration().get(section='repository', option='host'),
port=Configuration().get(section='repository', option='port'),
db=Configuration().get(section='repository', option='database')
), echo=False, pool_size=3)
self.session = sessionmaker(self.engine, expire_on_commit=False)
а затем вызовите self.session()
в разных потоках, у меня будет 3 разных соединения, которые используются в N разных потоках.
Означает ли это, что только 3 параллельных потока будут выполнять некоторую работу, в то время как другие будут ждать, пока один или несколько потоков не вызовут session.close()
? Или есть шанс, что> 2 потока будут использовать одно и то же соединение одновременно?
NullPool безопаснее (потому что каждый новый сеанс - это новое соединение) или нет?
self.engine = create_engine('postgresql://{user}:{password}@{host}:{port}/{db}'.format(
user=Configuration().get(section='repository', option='user'),
password=Configuration().get(section='repository', option='password'),
host=Configuration().get(section='repository', option='host'),
port=Configuration().get(section='repository', option='port'),
db=Configuration().get(section='repository', option='database')
), echo=False, poolclass=NullPool)
Общий вопрос: можно ли в таком случае использовать один и тот же пул соединений:
engine = create_engine('connection_string', echo=False, pool_size=3)
Session = sessionmaker(engine)
def some_function():
session = Session()
...
pool = Pool(processes=10)
pool.map(some_function)
pool.close()
pool.join()
Теперь кажется, я понял главную идею
Связанные: stackoverflow.com/questions/6297404/…, stackoverflow.com/questions/9619789/…, stackoverflow.com/questions/30734792/…
В целом, кажется, есть смесь между потоками и процессами. Вопрос начинается с вопроса, является ли пул соединений SQLAlchemy поточно-ориентированным, но заканчивается примером кода, в котором используется multiprocessing
. Краткий ответ на «общий вопрос»: нет, вы не должны совместно использовать движок и связанный с ним пул соединений через границы процесса, если используется разветвление. Но есть исключения.
Реализации пула сами по себе потокобезопасны и через прокси-сервер Engine
также является потокобезопасным, потому что механизм не сохраняет состояние в дополнение к сохранению ссылки на пул. С другой стороны, соединения, извлеченные из пула, - это нет потокобезопасный и ни Session
.
Documentation says that connection pool also is not designed for multithreading:
Есть небольшая ошибка в чтении, поскольку исходная цитата из документации касается совместного использования пулов соединений через границы процесс, если используется разветвление. Это, вероятно, приведет к проблемам, потому что под уровнями SQLAlchemy и DB-API обычно находится сокет TCP / IP или дескриптор файла, и с ними не следует работать одновременно.
В этом конкретном случае использование NullPool
было бы безопасным, в то время как другие - нет, поскольку он вообще не объединяется, и поэтому соединения не будут разделяться между процессами, если только один из них не сделает этого.
Does it mean that only 3 concurrent thread will do some work while others will wait until one or more thread will call
session.close()
?
Предполагая, что QueuePool
используется, установленный размер не является жестким пределом, и есть место для переполнения. Размер определяет количество подключений, которые будут постоянно храниться в пуле. Если предел переполнения достигнут, вызов будет ждать timeout
секунд, прежде чем отказаться и повысить TimeoutError
, если соединение не стало доступным.
Or there is a chance that >2 threads will use the same connection simultaneously?
Два или более потоки не смогут случайно проверить одно и то же соединение из пула, за исключением StaticPool
, но можно явно разделить его между потоками после (не делайте этого).
В конце концов, «Работа с двигателями и соединениями - базовое использование» покрывает основные части вопроса:
A single
Engine
manages many individual DBAPI connections on behalf of the process and is intended to be called upon in a concurrent fashion [emphasis added]....
For a multiple-process application that uses the
os.fork
system call, or for example the Pythonmultiprocessing
module, it’s usually required that a separateEngine
be used for each child process. This is because theEngine
maintains a reference to a connection pool that ultimately references DBAPI connections - these tend to not be portable across process boundaries. AnEngine
that is configured not to use pooling (which is achieved via the usage ofNullPool
) does not have this requirement.
Замечательный ответ! Спасибо! The question begins by asking if an SQLAlchemy connection pool is thread-safe, but ends with a code example that uses multiprocessing
наверное до сих пор путаю эти термины
процесс может состоять из одного или нескольких потоки, работающих одновременно и, возможно, параллельно. Линия между двумя время от времени может казаться немного размытым.
В случае, если это поможет кому-то еще - и это действительно ответ на другой вопрос, который будет:
Does SQLAlchemy use the same connection pool for all engines in the same thread?
Ответ - нет. Как указывает @ ilja-everila, SQLA надеется вы должны использовать один engine
для каждого процесса. Итак, если вы это сделаете
engine1 = create_engine(...)
engine2 = create_engine(...)
engine1.pool is engine2.pool # <- False
# so although pool_size=5, you can open more than 5 total connections
# because each engine has separate pools
connections1 = [engine1.connect() for _ in range(5)]
connections2 = [engine1.connect() for _ in range(5)]
Итак, если вы пришли сюда, задаваясь вопросом, почему вы исчерпываете свой max_connections
, а ваш код использует множество отдельных экземпляров engine
, даже если они находятся в одном потоке, вы не можете ожидать, что они будут совместно использовать пул соединений.
Пулы соединений могут быть потокобезопасными, но они уникальны для каждого экземпляра движка.
Таким образом, вы должны стремиться иметь один глобальный / одноэлементный экземпляр движка для своего приложения.
Учитесь на моем провале!
@ IljaEverilä, но я до сих пор не понимаю этой части: могу ли я использовать один и тот же пул соединений в нескольких потоках или нет?