C++ 如何确保只有一个互斥体?

C++ 如何确保只有一个互斥体?,c++,static,synchronization,mutex,C++,Static,Synchronization,Mutex,我在这里运行一些线程安全代码。我使用互斥来保护一段代码,这段代码一次只能由一个线程运行。我遇到的问题是使用这段代码时,有时会出现两个互斥对象。顺便说一下,这是一个静态函数。如何确保只创建1个互斥对象 /*static*/ MyClass::GetResource() { if (m_mutex == 0) { // make a new mutex object m_mutex = new MyMutex(); } m_mutex->Lock(); 问题是,在检查m_mutex是否为

我在这里运行一些线程安全代码。我使用互斥来保护一段代码,这段代码一次只能由一个线程运行。我遇到的问题是使用这段代码时,有时会出现两个互斥对象。顺便说一下,这是一个静态函数。如何确保只创建1个互斥对象

/*static*/ MyClass::GetResource()
{

if (m_mutex == 0)
{
// make a new mutex object
m_mutex = new MyMutex();
}

m_mutex->Lock();

问题是,在检查m_mutex是否为0后,线程可能会中断,但不会在创建互斥体之前中断,从而允许另一个线程运行相同的代码

不要立即分配给m_互斥体。创建一个新的互斥体,然后进行原子比较交换

您没有提到您的目标平台,但在Windows上:

MyClass::GetResource()
{
    if (m_mutex == 0)
    {
        // make a new mutex object
        MyMutex* mutex = new MyMutex();

        // Only set if mutex is still NULL.
        if (InterlockedCompareExchangePointer(&m_mutex, mutex, 0) != 0)
        {
           // someone else beat us to it.
           delete mutex;
        }
    }
    m_mutex->Lock();
否则,替换为平台提供的任何比较/交换功能


另一个选项是使用Windows Vista及更高版本上提供的支持,或者如果可以,只需预先创建互斥体。

只需在调用互斥体之前在
GetResource()之外创建
m_互斥体
,这将删除实际创建互斥体的关键部分

MyClass::Init()
{
  m_mutex = new Mutex;
}    

MyClass::GetResource()
{
  m_mutex->Lock();
  ...
  m_mutex->Unlock();
}

为什么要使用指针呢?为什么不将指针替换为不需要动态内存管理的实际实例?这避免了竞争条件,并且不会对函数的每次调用都造成性能影响。

惰性互斥体初始化并不真正适合静态方法;你需要一些保证,没有人竞相初始化。下面使用编译器为类生成单个静态互斥体

/* Header (.hxx) */
class MyClass
{
    ...

  private:
    static mutable MyMutex m_mutex;  // Declares, "this mutex exists, somewhere."
};


/* Compilation Unit (.cxx) */
MyMutex MyClass::m_mutex;            // The aforementioned, "somewhere."

MyClass::GetResource()
{
    m_mutex.Lock();
    ...
    m_mutex.Unlock();
}
static MyClass::GetResource()
{
    static MyMutex mutex;

    mutex.Lock();
    // ...
    mutex.Unlock();

其他一些解决方案需要其他程序员的额外假设。例如,使用“call init()”方法,您必须确保调用了初始化方法,并且每个人都必须知道这条规则。

因为它只保护代码的一个特定部分,所以只需在函数中声明它是静态的

static MyClass::GetResource()
{
    static MyMutex mutex;

    mutex.Lock();
    // ...
    mutex.Unlock();
该变量是具有静态存储持续时间的局部变量。标准中明确规定:

允许一个实现使用静态或静态变量对其他块作用域变量执行早期初始化 允许实现静态初始化的相同条件下的线程存储持续时间 命名空间范围内具有静态或线程存储持续时间的变量(3.6.2)。否则,这样的变量是 控件第一次通过其声明时初始化;这样的变量被认为是在 完成其初始化。如果初始化通过抛出异常退出,则初始化 未完成,因此将在控件下次输入声明时重试。如果控制进入 在初始化变量时并发声明,并发执行应等待 完成初始化


最后一句话你特别感兴趣。

什么平台?为什么不使用“真正的”系统级互斥?这些通常允许命名,以确保唯一性。命名互斥体令人讨厌。该语言已经提供了名称空间;为什么要为同步创建一个新的名称范围?@Andres:因为语言名称范围通常是错误的?例如,C++ NAMPULK最多与运行过程一样大。命名互斥体的名称空间通常是一个系统范围的名称空间。@原则上我同意MSalters,但我认为您夸大了它的好处。只有当您希望多个进程共享同一原语时,系统范围的名称空间才有价值。但是,绝大多数同步问题都是在一个进程中定义的。如果不需要,为什么要将问题公开给内核和其他进程?想法?您当前的示例存在内存泄漏-您需要确保删除丢失内存的互斥实例race@1800-谢谢你指出,如果有人照原样复制代码,那就太糟糕了。修正了。+1。尝试创建惰性互斥将导致一场灾难。MyClass::GetResource是一个静态函数:(@shergill在本例中也是MyClass::Init。关键是确保在调用GetResource之前调用Init()。@shergill-除非在main()之前调用
GetResource()
),在这种情况下,您会遇到更大的问题,应该很容易找到调用
Init()
的位置。