我使用互斥锁来锁定和解锁变量,因为我在更新周期中连续从主线程调用 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;
}
答案 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_t或LLVM 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正确实现,代码看起来正确。 有待提及的事情:
正如其他人所指出的,RAII优于宏观。
将accessFlag和_flag定义为volatile可能会更好。
如果您使用优化进行编译,我认为您获得的临时解决方案是不正确的。
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。
使用互斥锁来保护单个bool变量是很昂贵的,因为大多数时间花在切换OS模式上(从内核到用户来回)。使用自旋锁可能并不坏(细节代码取决于您的目标平台)。它应该是这样的:
spinlock_lock(&lock); _flag = value; spinlock_unlock(&lock);
atomic_set(&accessing_flag, true);
答案 3 :(得分:0)
您是否考虑过使用CRITICAL_SECTION?这仅适用于Windows,因此您失去了一些可移植性,但它是一个有效的用户级互斥。
答案 4 :(得分:0)
您提供的第二个代码块可能会在读取时修改标志,即使在单处理器设置中也是如此。 您发布的原始代码是正确的,并且在两个假设下不会导致死锁:
如果你想要一个便携式锁实现,我建议使用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;
}