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

时间:2011-11-26 04:25:24

标签: 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;

此类的用户(将同时存在多个)将希望使其成为此类的静态成员,以避免为该类的每个对象多次加载共享库:

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

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

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

2 个答案:

答案 0 :(得分:3)

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

  1. 如果两个线程第一次竞争访问锁定会怎么样? [即,你不能懒惰 - 创建锁]
  2. 如果过早地销毁锁会怎么样? [即,你不能静态创建锁] [/ li>

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

    在pthreads上,处理这个的最直接的方法是PTHREAD_MUTEX_INITIALIZER,它允许你静态初始化锁:

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

    在Windows上,您可以使用synchronous one-time initialization

    或者,如果你能保证在主要运行之前只有一个线程,你可以使用单例模式而不会破坏,只需强制在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;
    

    请注意,这使得可能不可移植(但很可能是真的)假设POD类型的静态对象在所有非POD静态对象都被销毁之前不会被销毁。我不知道任何平台不是这种情况。

答案 1 :(得分:1)

总结需求:需要多个LibLoader实例,每个实例用于不同的库,但必须存在单个锁,以确保它们不会覆盖彼此的错误代码。

一种方法是依赖文件中的静态初始化和销毁​​顺序。

更好的方法是不在LibLoader(等等)中设置NeedsALib静态字段。看起来这些客户端类可以在构造函数中传递右LibLoader的实例。

如果在客户端类之外创建LibLoader实例不方便,可以创建所有静态字段(锁定和加载器)指针,并使用具有延迟初始化的单例模式。然后在创建第一个加载器时,它最终也会创建锁。 Singleton本身需要锁定,但你可以在产生线程之前运行它。毁灭也是明确的,在你的控制之下。您也可以仅使用装载机执行此操作(保留静态锁定)。

此外,如果LibLoader没有很多要存储的状态,则可以使每个客户端类(NeedsALib等)实例化其自己的LibLoader。不过,这无疑是非常浪费的。