330
To ensure that all memory modifications performed inside the transaction are
durable on persistent memory once committed, the ranges tree is also used to iterate
over all snapshots and call the appropriate flushing function on the modified memory
locations.
Lazy Reinitialization of Variables: Storing
the Volatile State on Persistent Memory
While developing software for persistent memory, it is often useful to store the runtime
(volatile) state inside of persistent memory locations. Keeping that state consistent,
however, is extremely difficult, especially in multithreaded applications.
The problem is the initialization of the runtime state. One solution is to simply iterate
over all objects at the start of the application and initialize the volatile variables then, but
that might significantly contribute to startup time of applications with large persistent
pools. The other solution is to lazily reinitialize the variables on access, which is what
libpmemobj does for its built-in locks. The library also exposes this mechanism through
an API for use with custom algorithms.
Lazy reinitialization of the volatile state is implemented using a lock-free algorithm that
relies on a generation number stored alongside each volatile variable on persistent memory
and inside the pool header. The pool header resident copy is increased by two every time
a pool is opened. This means that a valid generation number is always even. When a
volatile variable is accessed, its generation number is checked against the one stored in
the pool header. If they match, it means that the object can be used and is simply returned
to the application; otherwise, the object needs to be initialized before returning to ensure
the initialization is thread-safe and is performed exactly once in a single instance of the
application.
The naive implementation could use a double-checked locking, where a thread
would try to acquire a lock prior to initialization and verify again if the generation
numbers match. If they still do not match, initialize the object, and increase the number.
To avoid the overhead that comes with using locks, the actual implementation first
uses a compare-and-swap to set the generation number to a value that is equal to the
generation number of the pool minus one, which is an odd number that indicates
an initialization operation is in progress. If this compare-and-swap were to fail, the
Chapter 16 pMDK Internals: IMportant algorIthMs anD Data struCtures
Précédent

- 354/457

Suivant