cx_Oracle, des générateurs, et des fils en python
-
22-09-2019 - |
Question
Quel est le comportement des curseurs cx_Oracle lorsque l'objet de connexion est utilisé par différents threads? Comment générateurs aurait une incidence sur ce comportement? Plus précisément ...
Modifier : La fonction d'exemple d'origine était incorrecte; un générateur a été retourné par une fonction secondaire, yield
n'a pas été utilisé directement dans la boucle. Cela indique clairement quand il finally
est exécuté (après return
fait), mais ne répond toujours pas si un curseur peut être utilisé si un autre thread commence à utiliser la connexion objet le curseur a été créé à partir. Il semble en fait (en Python 2.4, au moins), avec try...finally
yield
provoque une erreur de syntaxe.
def Get()
conn = pool.get()
try:
cursor = conn.cursor()
cursor.execute("select * from table ...")
return IterRows(cursor)
finally:
pool.put(conn)
def IterRows(cursor):
for r in cursor:
yield r
Get()
est une fonction appelée par plusieurs threads. Les connexions sont créées avec l'argument threaded=False
.
Je me demande ...
- est thread 1 l'objet de la
cursor
encore utilisable si le thread 2 arrive et utilise le même objet de connexion? Dans le cas contraire, ce qui pourrait arriver?
Le comportement que je vois est une exception dans cx_Oracle parler d'une erreur de protocole, puis une suite segfault.
La solution
Voir les docs : threadsafety
est, et je cite,
Actuellement 2, ce qui signifie que les fils peuvent partager le module et les connexions, mais pas les curseurs.
Ainsi, votre « pool de curseurs » construire (où un curseur peut être utilisé par différents threads) semble être au-delà du niveau de threadsafety
. Ce n'est pas une question de partage de connexions (c'est OK, puisque vous avez passé threaded
correctement dans le constructeur de la connexion) mais les curseurs. Vous pouvez stocker chaque curseur dans threading.local
après la première fois, un fil est utilisé, de sorte que chaque fil peut avoir son propre 1-curseur « pool » (pas une optimisation clé, cependant: faire un nouveau curseur n'est pas une lourde opération de service).
Wrt votre question 2, la clause finally
exécute lorsque l'objet générateur (construit par un appel à votre fonction générateur Get
) est fait - soit parce qu'il est élever StopIteration
, ou parce qu'il est recueillie (généralement des déchets parce que la dernière référence à elle vient de loin). Si l'appelant par exemple est:
def imthecaller():
for i, row in enumerate(Get()):
print i, row
if i > 1: break
# this is the moment the generators' finally-clause runs
print 'bye'
la finally
exécute après (au plus) 3 lignes ont été yield
ed.