编写一个可以用作静态但需要锁定的C++类

Gen*_*ent 6 c++ linux static locking shared-libraries

我需要编写加载共享库的类.dlopen()/ dlerror()序列需要锁定才能是线程安全的.

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;
Run Code Online (Sandbox Code Playgroud)

这个类的用户(同时会有多个用户)希望使它成为这个类的静态成员,以避免为该类的每个对象多次加载共享库:

class NeedsALib {
public:
NeedsALib() { if (!myLib.IsLoaded()) { myLib.Load(); } }
private:
static LibLoader myLib;
}
LibLoader::myLib;
Run Code Online (Sandbox Code Playgroud)

这段代码的问题在于它可能会崩溃,因为它依赖于程序终止时破坏静态的顺序.如果锁在myLib之前消失了它会崩溃....

如何以安全的方式编写线程安全且不依赖于静态破坏的顺序?

bdo*_*lan 3

不幸的是,我认为避免这种情况的唯一方法是使用不可移植的一次性初始化指令,并完全避免破坏锁。您需要处理两个基本问题:

  1. 如果两个线程第一次竞争访问锁会发生什么?[即,您不能延迟创建锁]
  2. 如果锁被过早销毁会发生什么?[即,您不能静态创建锁]

这些约束的组合迫使您使用不可移植的机制来创建锁。

在 pthread 上,处理此问题的最直接方法是使用PTHREAD_MUTEX_INITIALIZER,它允许您静态初始化锁:

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

// never destroyed
pthread_mutex_t LibLoader::mutex = PTHREAD_MUTEX_INITIALIZER;
Run Code Online (Sandbox Code Playgroud)

在 Windows 上,您可以使用同步一次性初始化

或者,如果您可以保证在 main 运行之前只有一个线程,您可以使用单例模式而无需破坏,只需在 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;
Run Code Online (Sandbox Code Playgroud)

请注意,这使得可能不可移植(但很可能为真)的假设是,在所有非 POD 静态对象都被销毁之前,POD 类型的静态对象不会被销毁。据我所知,没有哪个平台不是这样的。

  • IIRC,由于“pthread_mutex_t”是 POD 类型(因为它实际上是 C 结构),因此没有构造函数或析构函数,因此它在任何静态初始化发生之前进行初始化。我没有标准,或者我会引用它,但我可以说我见过的每种架构(ARM v9、ARM v7、ARM v7m、x86、x86_64、ppc、68k、68xx、MSP430 和 AVR) ,这样做,具体是:1)进程设置,2)POD初始化(`.data`部分从程序映像初始化,`.bss`部分清零),3)调用静态初始化器,最后4) 调用`main`。 (2认同)