互斥锁未解锁

时间:2013-02-27 15:19:53

标签: c++ mutex cocos2d-x

我使用互斥锁来锁定和解锁变量,因为我在更新周期中连续从主线程调用 getter ,并从另一个线程调用 setter 。我在

下面提供了setter和getter的代码

定义

bool _flag;
System::Mutex m_flag;

呼叫

#define LOCK(MUTEX_VAR) MUTEX_VAR.Lock();
#define UNLOCK(MUTEX_VAR) MUTEX_VAR.Unlock();

void LoadingScreen::SetFlag(bool value)
{
    LOCK(m_flag);
    _flag = value;
    UNLOCK(m_flag);
}

bool LoadingScreen::GetFlag()
{
    LOCK(m_flag);
    bool value = _flag;
    UNLOCK(m_flag);

    return value;
}

这种情况很有效,但有时变量在调用 SetFlag 时被锁定,因此它永远不会被设置从而干扰代码流。

有谁能告诉我如何解决这个问题?

编辑:

这是我最终做的解决方法。这只是一个临时解决方案。如果有人有更好的答案,请告诉我。

bool _flag;
bool accessingFlag = false;

void LoadingScreen::SetFlag(bool value)
{
    if(!accessingFlag)
    {
        _flag = value;
    }
}

bool LoadingScreen::GetFlag()
{
    accessingFlag = true;
    bool value = _flag;
    accessingFlag = false;

    return value;
}

6 个答案:

答案 0 :(得分:2)

首先,您应该使用RAII进行互斥锁定/解锁。其次,您要么不显示其他直接使用_flag的代码,要么您正在使用的互斥锁出现问题(不太可能)。什么库提供System :: Mutex?

答案 1 :(得分:2)

您遇到的问题(user1192878暗示)是由于编译器加载/存储延迟造成的。您需要使用memory barriers来实现代码。您可以声明volatile bool _flag;。但对于单CPU系统,compiler memory barriers不需要这样做。多CPU解决方案需要硬件障碍(在维基百科链接的下方);硬件障碍确保所有CPU都能看到本地处理器的内存/缓存。在这种情况下,不需要使用mutex和其他联锁。他们到底完成了什么?它们只会造成死锁而且不需要。

bool _flag;
#define memory_barrier __asm__ __volatile__ ("" ::: "memory") /* GCC */

void LoadingScreen::SetFlag(bool value)
{
    _flag = value;
    memory_barrier(); /* Ensure write happens immediately, even for in-lines */
}

bool LoadingScreen::GetFlag()
{
   bool value = _flag;
   memory_barrier(); /* Ensure read happens immediately, even for in-lines */
   return value;
}

只有在同时设置多个值时才需要互斥锁。您也可以将bool类型更改为sig_atomic_tLLVM atomics。但是,这是相当迂腐的,因为bool将适用于大多数实际的CPU架构。 Cocoa's concurrency pages也有关于替代API的一些信息来做同样的事情。我相信 gcc的内联汇编程序与Apple的编译器使用的语法相同;但那可能是错的。

API存在一些限制。实例GetFlag()返回,可以调用SetFlag()GetFlag()返回值是陈旧的。如果您有多个作家,那么您很容易错过一个SetFlag()。如果更高级别的逻辑倾向于ABA problems,这可能很重要。但是,所有这些问题都存在于/不使用互斥锁。 内存屏障仅解决了编译器/ CPU长时间缓存 SetFlag()的问题,并且会重新读取{GetFlag()中的值1}}。声明volatile bool flag通常会导致相同的行为,但会产生额外的副作用,并且无法解决多CPU问题。

std::atomic<bool> 根据stefan atomic_set(&accessing_flag, true);通常会在其实现中执行与上述相同的操作。如果您的平台上有它们,您可能希望使用它们。

答案 2 :(得分:1)

如果System :: Mutex正确实现,代码看起来正确。 有待提及的事情:

  1. 正如其他人所指出的,RAII优于宏观。

  2. 将accessFlag和_flag定义为volatile可能会更好。

  3. 如果您使用优化进行编译,我认为您获得的临时解决方案是不正确的。

    bool LoadingScreen::GetFlag()
    {
      accessingFlag = true;  // might be reordered or deleted
      bool value = _flag;  // might be optimized away
      accessingFlag = false;    // might be reordered before value set
      return value;   // might be optimized to directly returen _flag or register
    }
    
    在上面的代码中,优化器可能会做一些令人讨厌的事情。例如,没有什么可以阻止编译器取消对accessibleFlag = true的第一个赋值,或者它可以被重新排序,缓存。例如,对于编译器的观点,如果是单线程,则第一次赋值给accessFlag是没用的,因为从不使用值true。

  4. 使用互斥锁来保护单个bool变量是很昂贵的,因为大多数时间花在切换OS模式上(从内核到用户来回)。使用自旋锁可能并不坏(细节代码取决于您的目标平台)。它应该是这样的:

    spinlock_lock(&lock); _flag = value; spinlock_unlock(&lock);

  5. 此外,原子变量也很好。它可能看起来像:
  6. atomic_set(&accessing_flag, true);

答案 3 :(得分:0)

您是否考虑过使用CRITICAL_SECTION?这仅适用于Windows,因此您失去了一些可移植性,但它是一个有效的用户级互斥。

答案 4 :(得分:0)

您提供的第二个代码块可能会在读取时修改标志,即使在单处理器设置中也是如此。 您发布的原始代码是正确的,并且在两个假设下不会导致死锁:

  1. m_flags锁已正确初始化,未被任何其他代码修改。
  2. 锁实现是正确的。
  3. 如果你想要一个便携式锁实现,我建议使用OpenMP: How to use lock in openMP?

    从您的描述中,您似乎想要忙于等待线程处理某些输入。在这种情况下,stefans解决方案(声明标志std :: atomic)可能是最好的。在半合理的x86系统上,您还可以声明标志volatile int。对于未对齐的字段(打包结构),请不要这样做。

    你可以避免忙于等待两把锁。从机在完成处理时解锁第一个锁,并在等待从机完成时由主线程锁定。第二个锁在提供输入时由主线程解锁,在等待输入时由从机锁定。

答案 5 :(得分:-1)

这是我在某处看过的一种技术,但是找不到来源了。如果我找到它,我会编辑答案。基本上,编写器只会写,但读者将不止一次读取set变量的值,并且只有当所有副本一致时,它才会使用它。而且我已经更改了编写器,只要它与预期的值不匹配就会尝试继续写入值。

bool _flag;

void LoadingScreen::SetFlag(bool value)
{
    do
    {
       _flag = value;
    } while (_flag != value);
}

bool LoadingScreen::GetFlag()
{
    bool value;

    do
    {
        value = _flag;
    } while (value != _flag);

    return value;
}