将 std::atomic_flag 包裹在吸气剂/二传手中会使它的"atomicity"无效吗?

Does wrapping a std::atomic_flag in a getter/setter void its "atomicity"?

本文关键字:atomicity 无效 二传手 flag atomic std 包裹      更新时间:2023-10-16

说我有一个包含std::atomic_flag作为私人成员的类,通过getter暴露。类似以下内容(伪代码(:

class Thing
{
private:
    std::atomic_flag ready = ATOMIC_FLAG_INIT;
public:
    isReady()
    {
        return ready.test_and_set(); 
    }
} 

我的天真问题是:是否通过方法查询标志将其转化为非原子操作,是函数调用非原子(还是它?(?我应该让我的ready标志成为公共成员并直接查询吗?

不,不是。test_and_set()操作本身是原子,因此不同的呼叫堆栈有多深都没关系。

要证明这一点,请考虑直接"暴露" atomic_flag对象的基本情况:

static atomic_flag flag = ATOMIC_FLAG_INIT;
void threadMethod() {
    bool wasFirst = !flag.test_and_set();
    if( wasFirst ) cout << "I am thread " << this_thread::get_id() << ", I was first!"      << endl;
    else           cout << "I am thread " << this_thread::get_id() << ", I'm the runner-up" << endl;
}

如果两个线程输入 threadMethod-稍微稍微( t1(在另一个线程(t2(之前(CC_7(,那么我们可以期望控制台输出为以下(以相同的顺序(:

I am thread t1, I was first!
I am thread t2, I'm the runner-up

现在,如果这两个线程同时输入,但是t2t1领先微秒,但是t2然后比t1慢,因为它撰写了STDOUT,则输出将是:

I am thread t1, I'm the runner-up
I am thread t2, I was first!

...因此,即使输出不一定按预期顺序,对test_and_set的调用仍然是原子。

现在,如果您要用另一种方法将flag包装(不夹住,只是为了确定(,例如...

__declspec(noinline)
bool wrap() {
    return !flag.test_and_set();
}
void threadMethod() {
    bool wasFirst = wrap();
    if( wasFirst ) cout << "I am thread " << this_thread::get_id() << ", I was first!"      << endl;
    else           cout << "I am thread " << this_thread::get_id() << ", I'm the runner-up" << endl;
}

...那么该程序的行为不会有所不同 - 因为falsetrue返回test_and_set()的CC_17值仍将在每个线程的堆栈中。ergo,包装atomic_flag不会改变其原子。

C 原子的原子性属性确保中间不能破坏操作。也就是说,对于观察原子的第二个线程,它将在test_and_set之前观察状态或test_and_set之后的状态。这样的线程不可能在testset零件之间偷偷摸摸。

但是,这仅适用于操作本身。test_and_set呼叫完成后,所有赌注又一次关闭。您应该始终假设执行test_and_set的线程在完成该指令后可能会立即被预先享用,因此您不能假设在test_and_set之后执行的任何指令仍然会观察到相同的状态。

因此,添加函数调用不会在这里陷入困境。原子上的任何指令都必须假设原子变量的状态在此期间可能已经改变。Atomics通过提供以特殊方式设计的接口来考虑到这一点:例如,test_and_set返回测试结果,因为通过单独的调用获得该信息将不再是原子。

no, isReady()方法的工作原子与直接test_and_set()完全相同。