C++手工制作的互斥体

Kol*_*nya 3 c++ algorithm mutex atomic mutual-exclusion

我为我的项目制作了一个手工制作的互斥锁,但我怀疑它是否是线程安全的......

    bool blocked;

    while ( blocked )
    {

    }

    blocked = true;
    ...
    blocked = false;
Run Code Online (Sandbox Code Playgroud)

可以说,线程A传递while循环并且没有及时阻塞标志(没有时间将标志设置为false),线程B也通过while循环!

  1. 可能吗?为什么?

  2. 正如我所知,互斥体具有完全相同的工作原理.为什么不能在互斥锁中发生这种情况?我读过有关这不能被中断的原子操作......所以check-if-mutex-availablemutex-block不能中断,对不对?

Ker*_* SB 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
Run Code Online (Sandbox Code Playgroud)

(或者,您可以使用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
Run Code Online (Sandbox Code Playgroud)

原因是你只关心一个方向的正确排序:另一个方向的延迟更新并不是非常昂贵,但需要顺序一致性(默认情况下)可能很昂贵.


重要提示:上面的代码是一个所谓的自旋锁,因为当状态被锁定时,我们会进行忙转(while循环).这在几乎所有情况下都非常糟糕.内核提供的互斥系统调用是一个完全不同的鱼群,因为它允许线程向内核发出信号,它可以进入睡眠状态并让内核取消整个线程的计划.这几乎总是更好的行为.

  • 这个解决方案仍然不是例外.如果"关键工作"会引发异常怎么办?只需使用std :: mutex或boost :: mutex,它们的实现就足够快了 (2认同)