235
• Unfortunately, the program crashes after Thread 2 successfully
completes its transaction but before Thread 1 was able to finish its
transaction and flush its value.
This scenario leaves the application with an invalid, but consistent, value of x=10.
Since transactions are atomic, all changes done within them are not valid until they
successfully complete.
When the application starts, it knows it must perform a recovery operation due
to the previous crash and will replay the undo logs to rewind the partial update made
by Thread 1. The undo log restores the value of X=0, which was correct when Thread 1
added its entry. The expected value of X should be X=5 in this situation, but the undo log
puts X=0. You can probably see the huge potential for data corruption that this situation
can produce.
We describe concurrency for multithreaded applications in Chapter 14. Using
libpmemobj-cpp, the C++ language binding library to libpmemobj, concurrency issues
are very easy to resolve because the API allows us to pass a list of locks using lambda
functions when transactions are created. Chapter 8 discusses libpmemobj-cpp and
lambda functions in more detail.
Listing 12-27 shows how you can use a single mutex to lock a whole transaction. This
mutex can either be a standard mutex (std::mutex) if the mutex object resides in volatile
memory or a pmem mutex (pmem::obj::mutex) if the mutex object resides in persistent
memory.
Listing 12-27. Example of a libpmemobj++ transaction whose writes are both
atomic – with respect to persistent memory – and isolated – in a multithreaded
scenario. The mutex is passed to the transaction as a parameter
transaction::run (pop, [&] {
...
// all writes here are atomic and thread safe
...
}, mutex);
Consider the code in Listing 12-28 that simultaneously adds the same memory
region to two different transactions.
Chapter 12 Debugging persistent MeMory appliCations
• Unfortunately, the program crashes after Thread 2 successfully
completes its transaction but before Thread 1 was able to finish its
transaction and flush its value.
This scenario leaves the application with an invalid, but consistent, value of x=10.
Since transactions are atomic, all changes done within them are not valid until they
successfully complete.
When the application starts, it knows it must perform a recovery operation due
to the previous crash and will replay the undo logs to rewind the partial update made
by Thread 1. The undo log restores the value of X=0, which was correct when Thread 1
added its entry. The expected value of X should be X=5 in this situation, but the undo log
puts X=0. You can probably see the huge potential for data corruption that this situation
can produce.
We describe concurrency for multithreaded applications in Chapter 14. Using
libpmemobj-cpp, the C++ language binding library to libpmemobj, concurrency issues
are very easy to resolve because the API allows us to pass a list of locks using lambda
functions when transactions are created. Chapter 8 discusses libpmemobj-cpp and
lambda functions in more detail.
Listing 12-27 shows how you can use a single mutex to lock a whole transaction. This
mutex can either be a standard mutex (std::mutex) if the mutex object resides in volatile
memory or a pmem mutex (pmem::obj::mutex) if the mutex object resides in persistent
memory.
Listing 12-27. Example of a libpmemobj++ transaction whose writes are both
atomic – with respect to persistent memory – and isolated – in a multithreaded
scenario. The mutex is passed to the transaction as a parameter
transaction::run (pop, [&] {
...
// all writes here are atomic and thread safe
...
}, mutex);
Consider the code in Listing 12-28 that simultaneously adds the same memory
region to two different transactions.
Chapter 12 Debugging persistent MeMory appliCations
