Concurrency¶
Let us see how sheraf cowboys behave in parallelize contexts.
>>> class Cowboy(sheraf.Model):
... table = "cowboys"
... gunskill = sheraf.IntegerAttribute()
...
>>> db = sheraf.Database("zeo://localhost:{}".format(zeo_port))
...
>>> with sheraf.connection(commit=True):
... george = Cowboy.create()
Threading¶
The ZODB documentation about concurrency states that database Connection
, ITransactionManager
and ITransaction
are not thread-safe. However ZODB DB
objects can be shared between threads.
This means that it is possible to create a Database
object once, and then share it on several threads. However each thread should use its own connection context:
>>> import threading
>>> def practice_gun(cowboy_id):
... # The database is available in children thread, but they
... # need to open their own connection contexts.
... with sheraf.connection(commit=True):
... cowboy = Cowboy.read(cowboy_id)
... cowboy.gunskill = cowboy.gunskill + 1000
...
>>> practice_session = threading.Thread(target=practice_gun, args=(george.id,))
>>> practice_session.start()
>>> practice_session.join()
...
>>> with sheraf.connection():
... Cowboy.read(george.id).gunskill
1000
Opening a thread within a connection context will produce various unexpected behaviors.
Multiprocessing¶
When using multiprocessing, the behavior is a bit different. The Database
are not shared between processes.
>>> import multiprocessing
>>> practice_session = multiprocessing.Process(target=practice_gun, args=(george.id,))
>>> practice_session.start()
>>> practice_session.join()
>>> practice_session.exitcode
1
The connection context in the practice_gun
function has raised a KeyError
exception because in this new process, no database has been defined. Fortunately there is a simple solution to this. The database needs to be redefined in the new process:
>>> def recreate_db_and_practice_gun(cowboy_id):
... # The database is re-created in the child process
... db = sheraf.Database("zeo://localhost:{}".format(zeo_port))
...
... with sheraf.connection(commit=True):
... cowboy = Cowboy.read(cowboy_id)
... cowboy.gunskill = cowboy.gunskill + 1000
... db.close()
...
>>> practice_session = multiprocessing.Process(target=recreate_db_and_practice_gun, args=(george.id,))
>>> practice_session.start()
>>> practice_session.join()
...
>>> with sheraf.connection():
... Cowboy.read(george.id).gunskill
2000
Note
Remember that FileStorage
, MappingStorage
and DemoStorage
cannot be used by several processes.