其他分享
首页 > 其他分享> > c – std :: mutex是否顺序一致?

c – std :: mutex是否顺序一致?

作者:互联网

比方说,我有两个线程A和B分别写入全局布尔变量fA和fB,它们最初设置为false并分别受std :: mutex对象mA和mB保护:

// Thread A
mA.lock();
assert( fA == false );
fA = true;
mA.unlock();

// Thread B
mB.lock()
assert( fB == false );
fB = true;
mB.unlock()

是否有可能在不同的线程C和D中以不同的顺序观察对fA和fB的修改?换句话说,可以是以下程序

#include <atomic>
#include <cassert>
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;

mutex mA, mB, coutMutex;
bool fA = false, fB = false;

int main()
{
    thread A{ []{
            lock_guard<mutex> lock{mA};
            fA = true;
        } };
    thread B{ [] {
            lock_guard<mutex> lock{mB};
            fB = true;
        } };
    thread C{ [] { // reads fA, then fB
            mA.lock();
            const auto _1 = fA;
            mA.unlock();
            mB.lock();
            const auto _2 = fB;
            mB.unlock();
            lock_guard<mutex> lock{coutMutex};
            cout << "Thread C: fA = " << _1 << ", fB = " << _2 << endl;
        } };
    thread D{ [] { // reads fB, then fA (i. e. vice versa)
            mB.lock();
            const auto _3 = fB;
            mB.unlock();
            mA.lock();
            const auto _4 = fA;
            mA.unlock();
            lock_guard<mutex> lock{coutMutex};
            cout << "Thread D: fA = " << _4 << ", fB = " << _3 << endl;
        } };
    A.join(); B.join(); C.join(); D.join();
}

合法打印

Thread C: fA = 1, fB = 0
Thread D: fA = 0, fB = 1

根据C标准?

注意:可以使用std :: atomic< bool>实现自旋锁定.变量使用顺序一致的内存顺序或获取/释放内存顺序.所以问题是std :: mutex是否表现为顺序一致的自旋锁或获取/释放内存顺序自旋锁.

解决方法:

是的,这是允许的输出是不可能的,但是std :: mutex不一定是顺序一致的.获取/释放足以排除该行为.

std :: mutex在标准中没有定义为顺序一致,只有那个

30.4.1.2 Mutex types [thread.mutex.requirements.mutex]

11 Synchronization: Prior unlock() operations on the same object shall
synchronize with (1.10) this operation [lock()].

Synchronize-with似乎与std :: memory_order :: release / acquire相同(见this question).
据我所知,获取/释放自旋锁将满足std :: mutex的标准.

大编辑:

但是,我不认为这意味着你的想法(或我的想法).输出仍然是不可能的,因为获取/释放语义足以排除它.这是一个微妙的观点,更好地解释了here.起初看起来显然是不可能的,但我认为对这样的事情保持谨慎是正确的.

从标准,unlock()与lock()同步.这意味着在lock()之后发生的任何事情在lock()之后都可见.之前发生的事情(以下称为>)是在上面的链接中更好地解释的一个有点奇怪的关系,但是因为在这个例子中有一些互斥体,所有东西都像你期望的那样工作,即const auto _1 = fA;在const auto _2 = fB;之前发生,并且当解锁()时,线程可见的任何更改对于锁定()互斥锁的下一个线程是可见的互斥锁.它也具有一些预期的性质,例如如果X在Y之前发生而Y在Z之前发生,那么X – > Z,如果X在Y之前发生,那么Y在X之前不会发生.

从这里开始,不难看出看似直观正确的矛盾.

简而言之,每个互斥锁都有一个明确定义的操作顺序 – 例如对于互斥锁A,线程A,C,D按某种顺序保存锁.对于线程D打印fA = 0,它必须在线程A之前锁定mA,反之则在线程C锁定.所以
mA的锁定顺序是D(mA) – > A(mA) – > C(毫安).

对于互斥体B,序列必须是C(mB) – > B(mB) – > d(MB).

但是从我们知道的程序C(mA) – > C(mB),这样我们就可以将两者放在一起得到D(mA) – > A(mA) – > C(mA) – > C(mB) – > B(mB) – > D(mB),表示D(mA) – > d(MB).但是代码也给了我们D(mB) – > D(mA),这是一个矛盾,意味着你的观察输出是不可能的.

这个结果对于获取/释放自旋锁没有什么不同,我认为每个人都混淆了对变量的常规获取/释放内存访问,并且可以访问受自旋锁保护的变量.不同之处在于,使用自旋锁,读取线程还执行比较/交换和释放写入,这与单个版本写入和获取读取完全不同.

如果您使用顺序一致的自旋锁,那么这不会影响输出.唯一的区别是你总是可以从一个没有获得任何锁定的单独线程中明确地回答诸如“互斥锁A在互斥锁B之前被锁定”之类的问题.但是对于这个例子和大多数其他例子,这种陈述没有用,因此获得/释放是标准.

标签:memory-barriers,c,c11,multithreading,mutex
来源: https://codeday.me/bug/20191006/1861660.html