3

当我在更新周期中不断地从主线程调用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;
}
4

6 回答 6

2

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

于 2013-02-27T16:20:30.427 回答
2

您遇到的问题(user1192878暗示)是由于编译器加载/存储延迟造成的。您需要使用内存屏障来实现代码。您可以声明volatile bool _flag;. 但是 对于单个 CPU 系统,编译器内存屏障不需要这样做。多 CPU 解决方案需要硬件屏障(位于 Wikipedia 链接下方);硬件屏障确保所有 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 的并发页面也有一些关于替代 API 的信息来做同样的事情。我相信gcc 的内联汇编器与 Apple 编译器使用的语法相同。但这可能是错误的。

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

std::atomic<bool>As per stefan and atomic_set(&accessing_flag, true); will generally do the same thing as describe above in their implementations. You may wish to use them if they are available on your platforms.

于 2013-03-10T21:35:43.177 回答
1

如果正确实现了 System::Mutex,则代码看起来正确。有一点要提:

  1. 正如其他人指出的那样,RAII 比宏要好。

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

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

    bool LoadingScreen::GetFlag()
    {
      访问标志 = 真;// 可能被重新排序或删除
      布尔值 = _flag; // 可能会被优化掉
      访问标志 = 假;// 可能在设置值之前重新排序
      返回值;// 可能会被优化为直接返回_flag或注册
    }
    
    在上面的代码中,优化器可能会做一些讨厌的事情。例如,没有什么可以阻止编译器消除对 accessFlag=true 的第一个赋值,或者它可以被重新排序、缓存。例如,从编译器的角度来看,如果是单线程的,那么第一次分配给 accessFlag 是没有用的,因为值 true 从未使用过。

  4. 使用互斥锁来保护单个布尔变量是昂贵的,因为大部分时间都花在切换操作系统模式(从内核到用户来回)上。使用自旋锁可能还不错(详细代码取决于您的目标平台)。它应该是这样的:

    自旋锁锁(&锁);_flag = 值;自旋锁解锁(&锁);

  5. 原子变量在这里也很好。它可能看起来像:
atomic_set(&accessing_flag, true);
于 2013-03-10T17:17:45.330 回答
0

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

于 2013-03-06T17:29:54.443 回答
0

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

  1. m_flags 锁已正确初始化,并且未被任何其他代码修改。
  2. 锁的实现是正确的。

如果你想要一个可移植的锁实现,我建议使用 OpenMP: 如何在 openMP 中使用锁?

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

您可以避免用两个锁忙等待。第一个锁在slave完成处理时解锁,并在等待slave完成时由主线程锁定。第二个锁在提供输入时由主线程解锁,在等待输入时由从属线程上锁。

于 2013-03-10T16:17:41.613 回答
-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;
}
于 2013-03-10T14:03:46.833 回答