我为我的项目制作了一个手工制作的互斥锁,但我怀疑它是否是线程安全的......
bool blocked;
while ( blocked )
{
}
blocked = true;
...
blocked = false;
让我们说,线程A传递while循环并且没有及时阻塞标志(没有时间将标志设置为false
)而线程B也通过while循环!
有可能吗?为什么呢?
当我得到它时,互斥体具有完全相同的工作原理。为什么不能在互斥锁中发生这种情况?我读过关于无法中断的原子操作......所以check-if-mutex-available
和mutex-block
不能被打断,对吧?
答案 0 :(得分:8)
您的代码完全不存在了!
原因是对变量blocked
的访问不是原子的。如果在第一个线程写出true
更新并且更新传播到所有CPU之前发生了两次读取,则两个线程可以同时读取并确定互斥锁已解锁。
你需要原子变量和原子交换来解决这个问题。 atomic_flag
类型正是您想要的:
#include <atomic>
std::atomic_flag blocked;
while (blocked.test_and_set()) { } // spin while "true"
// critical work goes here
blocked.clear(); // unlock
(或者,您可以使用std::atomic<bool>
和exchange(true)
,但atomic_flag
专门用于此目的。)
如果这是单线程上下文,原子变量不仅会阻止编译器重新排序出现的代码,而且它们还会使编译器生成必要的代码以防止CPU本身以一种允许不一致的执行流程的方式重新排序指令。
事实上,如果你想提高效率,你可以在集合和清除操作上要求更便宜的内存排序,如下所示:
while (blocked.test_and_set(std::memory_order_acquire)) { } // lock
// ...
blocked.clear(std::memory_order_release); // unlock
原因是你只关心一个方向的正确排序:另一个方向的延迟更新不是很昂贵,但需要顺序一致性(默认情况下)可能很昂贵。
重要提示:以上代码是所谓的自旋锁,因为当状态被锁定时,我们会进行忙碌旋转(while
循环)。这在几乎所有情况下都非常糟糕。内核提供的互斥系统调用是一个完全不同的鱼群,因为它允许线程向内核发出信号,它可以进入睡眠状态并让内核取消整个线程的计划。这几乎总是更好的行为。
答案 1 :(得分:3)
例如,在Windows上,您可以使互斥锁看起来像this。
答案 2 :(得分:1)
你已经得到了它。
是的,这很有可能。对于单核,线程由操作系统通过timeslicing执行。它运行线程A一点,然后暂停它,并运行线程B一点。传递while循环后,线程A可能会暂停。
为了解决这些问题,CPU已经实现了不能被任何东西打断的特殊指令。这些原子操作由互斥锁用于检查标志,并在一次操作中设置它。
答案 3 :(得分:1)
是的,您所描述的情况可能会发生。这样做的原因是线程可以在测试blocked
为false
和将blocked
设置为true
之间中断。要获得所需的行为,您需要使用或模拟原子测试和设置操作。
有关测试和设置的更多信息,请参见here。
答案 4 :(得分:1)
std::mutex
(理想情况下与std::lock
一起使用),对于C ++ 03,您可以使用boost::mutex
等。