Writing a C ++ class that can be used as static, but requires locking

I need to write a class that loads shared libraries. The dlopen () / dlerror () sequence requires locking to ensure thread safety.

class LibLoader { public: LibLoader(string whichLib); bool Load() { Wait(lock); ... dlopen() ... dlerror() ... } bool Unload() { Wait(lock); ... dlclose() ... dlerror() ... } bool IsLoaded() {...} // ... access to symbols... private: static Lock lock; } Lock Lock::lock; 

Users of this class (there will be several at the same time) will want to make it a static member of this class, so as not to load the shared library several times for each class object:

 class NeedsALib { public: NeedsALib() { if (!myLib.IsLoaded()) { myLib.Load(); } } private: static LibLoader myLib; } LibLoader::myLib; 

The problem with this code is that it can // crash, because it relies on the order in which statics are destroyed when the program terminates. If the lock is gone before myLib, it will work ....

How can this be written in a safe manner that is thread safe and does not depend on the order of static fracture?

+6
source share
2 answers

Unfortunately, I believe that the only way to avoid this is to use non-portable one-time initialization directives and not destroy the lock at all. You need to solve two main problems:

  • What happens if two threads start accessing the lock for the first time? [those. you cannot be lazy, create a lock]
  • What happens if the lock is destroyed too soon? [that is, you cannot statically create a lock]

The combination of these restrictions forces you to use an intolerable mechanism to create a lock.

In pthreads, the easiest way to handle this is with PTHREAD_MUTEX_INITIALIZER , which allows you to statically initialize locks:

 class LibLoader{ static pthread_mutex_t mutex; // ... }; // never destroyed pthread_mutex_t LibLoader::mutex = PTHREAD_MUTEX_INITIALIZER; 

In windows you can use synchronous one-time initialization .

Alternatively, if you can guarantee that there will be only one thread before the main launches, you can use a singleton pattern without breaking and just force the lock to touch before main ():

 class LibLoader { class init_helper { init_helper() { LibLoader::getLock(); } }; static init_helper _ih; static Lock *_theLock; static Lock *getLock() { if (!_theLock) _theLock = new Lock(); return _theLock; } // ... }; static init_helper LibLoader::_ih; static Lock *LibLoader::_theLock; 

Note that this makes it impossible for the hypothetical (but most likely true) assumption that static objects of type POD will not be destroyed until all static objects other than POD are destroyed. I do not know any platform in which this is not so.

+3
source

Completion of requirements: several instances of LibLoader , each for a different library, but to ensure they do not overwrite each other's error codes, one lock must exist.

One way is to rely on static initialization and the destruction order in the file.

It is best not to set the LibLoader static field in NeedsALib (and the like). It seems that an instance of the right LibLoader in the constructor can be passed to these client classes.

If creating instances of LibLoader outside its client classes is not suitable, you can make all the pointers to static fields (locks and loaders) and use a singleton template with lazy initialization. Then, when you create the first bootloader, it also creates a lock. The singleton itself would require a lock here, but you might have started it before your threads appeared. Destruction will also be explicit and under your control. You can also do this only with bootloaders (while maintaining a static lock).

In addition, if LibLoader does not have a large number of storage states, you can make each client class ( NeedsALib , etc.) an instance of its own LibLoader . This is admittedly rather wasteful.

+1
source

Source: https://habr.com/ru/post/902432/


All Articles