An introduction to pmemobj (part 6) - threading
All of the pmemobj library functions are thread-safe, with following two exceptions: pool management functions (open, close and friends) and pmemobj_root
when providing different sizes in different threads - so as long as you are using this function the way it’s meant to be used you don’t have to worry about it. As for macros - generally only the FOREACH macros are not thread-safe for obvious reasons.
Synchronization
If you need to put a lock inside a structure that resides on persistent memory, our library provides pthread-like API for that purpose. There’s no need to initialize those locks or to verify their state. When an application crashes they are all automatically unlocked. As for the reason why something like this is needed, consider following code:
struct foo {
pthread_mutex_t lock;
int bar;
};
int fetch_and_add(TOID(struct foo) foo, int val) {
pthread_mutex_lock(&D_RW(foo)->lock);
int ret = D_RO(foo)->bar;
D_RW(foo)->bar += val;
pthread_mutex_unlock(&D_RW(foo)->lock);
return ret;
}
If a crash happens, well anywhere in fetch_and_add
really, the pthread_mutex_t
structure will contain invalid values and the application will most likely segfault when an attempt to use it is made. The solution to that would be to call pthread_mutex_init
on every single pmem-resident lock. Manually. Here’s the proper way to do it:
struct foo {
PMEMmutex lock;
int bar;
};
int fetch_and_add(TOID(struct foo) foo, int val) {
pmemobj_mutex_lock(pop, &D_RW(foo)->lock);
int ret = D_RO(foo)->bar;
D_RW(foo)->bar += val;
pmemobj_mutex_unlock(pop, &D_RW(foo)->lock);
return ret;
}
Transactions
A single transaction block works in the context of a single thread. And that’s it. When we were considering the performance ramifications of multi-threaded transaction we came to the conclusion that it’s simply not worth it - there are other ways of parallelizing problems. As an example, take a look at the PI example here.