280
Listing 14-2. Example of incorrect synchronization inside a PMDK transaction
46 struct root {
47
pobj::mutex mtx;
48
pobj::p counter;
49 };
50
51 using pop_type = pobj::pool;
52
53 void increment(pop_type &pop) {
54
auto proot = pop.root();
55
pobj::transaction::run(pop, [&] {
56
std::unique_lock lock(proot->mtx);
57
proot->counter.get_rw() += 1;
58
});
59 }
• Line 47: We added a mutex to the root data structure.
• Line 56: We acquired the mutex lock within the transaction before
incrementing the value of counter to avoid a race condition. Each
thread increments the counter inside the critical section protected by
the mutex.
Now if we run this example multiple times, it will always increment the value of
the counter stored in persistent memory by 1. But we are not done yet. Unfortunately,
the example in Listing 14-2 is also wrong and can cause data inconsistency issues
on persistent memory. The example works well if there are no transaction aborts.
However, if the transaction aborts after the lock is released but before the transaction
has completed and successfully committed its update to persistent memory, other
threads can read a cached value of the counter that can cause data inconsistency issues.
To understand the problem, you need to know how libpmemobj transactions work
internally. For now, we discuss only the necessary details required to understand this
issue and leave the in-depth discussion of transactions and their implementation for
Chapter 16.
A libpmemobj transaction guarantees atomicity by tracking changes in the undo log.
In the case of a failure or transaction abort, the old values for uncommitted changes are
restored from the undo log. It is important to know that the undo log is a thread-specific
Chapter 14 ConCurrenCy and persistent MeMory
Précédent

- 304/457

Suivant