Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing a C++ class that can be used as static, but needs a lock

I need to write class that loads shared libraries. The dlopen() / dlerror() sequence needs a lock to be thread safe.

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;

The users of this class (there will be multiple at the same time) will want to make it a static member of this class to avoid loading a shared library multiple time for every object of the class:

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

The problem with this code is that it may / will crash, because it relies on the order of statics being destructed when the program terminates. If the lock is gone before myLib it will crash....

How can this be written in a safe manner that is thread safe and doesn't rely on the order of static destruction ?

like image 508
Gene Vincent Avatar asked Nov 26 '11 04:11

Gene Vincent


2 Answers

Unfortunately, I think the only way to avoid this is to both use unportable one-time initialization directives, and to avoid destroying the lock at all. There are two basic problems you need to deal with:

  1. What happens if two threads race to access the lock for the first time? [ie, you cannot lazy-create the lock]
  2. What happens if the lock is destroyed too early? [ie, you cannot statically-create the lock]

The combination of these constraints forces you to use a non-portable mechanism to create the lock.

On pthreads, the most straightforward way to handle this is with the PTHREAD_MUTEX_INITIALIZER, which lets you statically initialize locks:

class LibLoader{
  static pthread_mutex_t mutex;
// ...
};

// never destroyed
pthread_mutex_t LibLoader::mutex = PTHREAD_MUTEX_INITIALIZER;

On windows, you can use synchronous one-time initialization.

Alternately, if you can guarentee that there will only be one thread before main runs, you can use the singleton pattern without destruction, and just force the lock to be touched 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 the possibly non-portable (but highly likely to be true) assumption that static objects of POD type are not destroyed until all non-POD static objects have been destroyed. I'm not aware of any platform in which this is not the case.

like image 120
bdonlan Avatar answered Oct 20 '22 19:10

bdonlan


Wrapping up the requirements: multiple LibLoader instances are needed, each for a different library, but a single lock must exist to ensure they don't overwrite each others' error codes.

One way would be to rely on static initialization and destruction order within a file.

Better way would be not to make LibLoader static field in NeedsALib (and the like). It seems these client classes can be passed an instance of the right LibLoader in the constructor.

If creating LibLoader instances outside of its client classes isn't convenient, you can make all static fields (the lock and the loaders) pointers and use singleton pattern with lazy initialization. Then as you create the first loader, it ends up creating the lock as well. Singleton itself would require locking here, but you could perhaps run it before spawning your threads. Destruction would also be explicit and under your control. You can also do this with loaders only (retaining static lock).

Also, if LibLoader doesn't have a lot of state to store, you can make each client class (NeedsALib etc) instantiate its own LibLoader. This is admittedly quite wasteful, though.

like image 25
Adam Zalcman Avatar answered Oct 20 '22 19:10

Adam Zalcman