访问std :: atomic bool变量的顺序

Abh*_*yal 1 c++ linux concurrency multithreading c++11

我有一个访问(读取和写入)std::atomic<bool>变量的函数.我试图理解指令的执行顺序,以便决定原子是否足够,或者我将在这里使用互斥.功能如下 -

// somewhere member var 'executing' is defined as std::atomic<bool>`

int A::something(){

    int result = 0;
    // my intention is only one thread should enter next block
    // others should just return 0
    if(!executing){
        executing = true;

        ...
        // do some really long processing
        ...

        result    = processed;
        executing = false;
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)

我在cppreference上看过这个页面,提到 -

std :: atomic模板的每个实例化和完全特化都定义了一个原子类型.如果一个线程写入原子对象而另一个线程从中读取,则行为是明确定义的(有关数据争用的详细信息,请参阅内存模型)

在内存模型页面上提到以下内容 -

当表达式的评估写入内存位置而另一个评估读取或修改相同的内存位置时,表达式会发生冲突.具有两个冲突评估的程序具有数据竞争,除非两者之一

  • 两个冲突的评估都是原子操作(参见std :: atomic)

  • 其中一个冲突的评估发生在另一个之前(参见std :: memory_order)

如果发生数据争用,则程序的行为未定义.

略低于它读 -

当线程从内存位置读取值时,它可能会看到初始值,写在同一线程中的值或写在另一个线程中的值.有关从线程写入的命令对其他线程可见的顺序的详细信息,请参阅std :: memory_order.


这对我来说有点混乱,上面3个陈述中的哪一个实际上发生在这里?

当我执行if(!executing){这个指令时这是一个原子指令吗?更重要的是 - 是否保证没有其他线程将进入if循环,如果一个两个线程将进入,如果正文,因为第一个将设置executingtrue

如果提到的代码有问题,我应该如何重写它以反映原始意图..

Dra*_*-On 5

如果我理解正确,那么您正在尝试确保只有一个线程可以同时执行一段代码.这正是互斥体的作用.由于您提到如果互斥锁不可用,您不希望线程阻塞,您可能需要查看try_lock()方法std::mutex.请参阅std :: mutex文档.

现在为什么你的代码没有按预期工作:简化一点,std :: atomic保证在并发访问变量时不会有数据争用.即,有一个定义明确的读写顺序.这不足以满足您的要求.想象一下if分支:

if(!executing) {
   executing = true;
Run Code Online (Sandbox Code Playgroud)

请记住,只有读写操作executing才是原子的.这至少留下了否定!if自身不同步.使用两个线程,执行顺序可能如下所示:

  1. 线程1读取executing(原子),值为false
  2. 线程1否定从executingvalue = true 读取的值
  3. 线程1评估条件并进入分支
  4. 线程2读取executing(原子地),值为false
  5. 线程1设置executing为true
  6. 线程2否定了该值,该值被读作false并且现在再次为真
  7. 线程2进入分支......

现在两个线程都进入了分支.

我会建议这些方面:

std::mutex myMutex;

int A::something(){

    int result = 0;
    // my intention is only one thread should enter next block
    // others should just return 0
    if(myMutex.try_lock()){

        ...
        // do some really long processing
        ...

        result    = processed;
        myMutex.unlock();
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)


Use*_*ess 2

您可以使用 std::atomic_bool 来确保代码块中的互斥吗?

是的。std::atomic_bool足以实现自旋锁,它比std::mutex其他答案中的更简单(尽管也不太通用且默认值更差),但仍然比您实际需要的更复杂。

你的问题在这里:

当我执行if(!executing){...是否保证没有其他线程会进入该 if 循环,如果两个线程将进入该 if 主体,因为第一个线程将设置executingtrue

加载(用于比较)和存储都是单独的原子操作,但它们不是单个原子操作,这实际上意味着它们是可整除的(被不同线程中的另一个操作整除)。

然而,有一个原子加载+比较+存储正是用于此目的:compare_exchange

你的代码应该是这样的:

int A::something() {
    
    int result = 0;

    // if executing is false,
    // atomically set it to true
    bool expect_executing = false;
    if(executing.compare_exchange_strong(
        expect_executing, true)) {

        // enter the branch only if the exchange succeeded
        // (executing was false and is now true)
        // ... do some really long processing ...
       
        result = processed;
        executing = false;
    }
    
    return result;
}
Run Code Online (Sandbox Code Playgroud)

你可以做类似的事情std::atomic_flag::test_and_set,但我坚持使用您现有的类型。

您还可以削弱默认(顺序一致性)排序。